From 4f255358905ae40c835419cbb95affe0d824da39 Mon Sep 17 00:00:00 2001 From: 92Infinitus92 <92georgepetroff92@gmail.com> Date: Fri, 28 Aug 2026 15:36:14 +0300 Subject: [PATCH 1/6] feat(phoenix): add Eternal state preparation --- Cargo.lock | 75 +- Cargo.toml | 2 + crates/core/Cargo.toml | 2 + crates/core/src/scenarios/README.md | 1 + .../phoenix-eternal-liquidation-cascade.json | 41 + crates/core/src/scenarios/protocols/mod.rs | 2 + .../protocols/phoenix-eternal/README.md | 154 ++ .../protocols/phoenix-eternal/mod.rs | 1 + .../phoenix-eternal/v1/editor-schema.json | 74 + .../protocols/phoenix-eternal/v1/mod.rs | 1 + .../phoenix-eternal/v1/overrides.yaml | 312 ++++ .../phoenix-eternal/v1/state_builder.rs | 1314 +++++++++++++++++ crates/core/src/scenarios/registry.rs | 30 +- crates/core/src/surfnet/svm.rs | 118 +- crates/core/src/tests/live.rs | 60 + crates/core/src/tests/mod.rs | 4 + crates/core/src/tests/phoenix/mod.rs | 1093 ++++++++++++++ crates/core/src/tests/pump/mod.rs | 40 +- crates/mcp/src/surfpool/mod.rs | 92 ++ 19 files changed, 3372 insertions(+), 44 deletions(-) create mode 100644 crates/core/src/scenarios/examples/phoenix-eternal-liquidation-cascade.json create mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/README.md create mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/mod.rs create mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/v1/editor-schema.json create mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/v1/mod.rs create mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml create mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs create mode 100644 crates/core/src/tests/live.rs create mode 100644 crates/core/src/tests/phoenix/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 95c058375..3e418bd69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,6 +1212,12 @@ dependencies = [ "thiserror 2.0.20", ] +[[package]] +name = "az" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" + [[package]] name = "base16ct" version = "0.2.0" @@ -3212,6 +3218,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059c31d7d36c43fe39d89e55711858b4da8be7eb6dabac23c7289b1a19489406" +[[package]] +name = "fixed" +version = "1.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -5990,6 +6008,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pastey" version = "0.2.3" @@ -6110,6 +6134,35 @@ dependencies = [ "serde", ] +[[package]] +name = "phoenix-rise-accounts" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b130422dfd976cef8d5f82394cbb5491d18355a77eaead7fa092b90844bdd" +dependencies = [ + "borsh 1.7.0", + "bytemuck", + "phoenix-rise-math", + "sha2-const-stable", + "solana-pubkey 3.0.0", + "thiserror 2.0.20", +] + +[[package]] +name = "phoenix-rise-math" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6c10d1a8fd19901084fe96a17404e2946a0de9645ad788dc096fa986fed7a3" +dependencies = [ + "borsh 1.7.0", + "bytemuck", + "fixed", + "pastey 0.1.1", + "rust_decimal", + "sha2-const-stable", + "thiserror 2.0.20", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -7036,6 +7089,23 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust_decimal" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" +dependencies = [ + "arrayvec", + "borsh 1.7.0", + "bytes 1.12.1", + "num-traits", + "rand 0.8.6", + "rand 0.9.4", + "serde", + "serde_json", + "wasm-bindgen", +] + [[package]] name = "rustc-demangle" version = "0.1.28" @@ -12124,6 +12194,8 @@ dependencies = [ "opentelemetry-prometheus", "opentelemetry_sdk 0.28.0", "p256", + "phoenix-rise-accounts", + "phoenix-rise-math", "prometheus", "reqwest 0.12.28", "scheduled-thread-pool", @@ -13450,6 +13522,7 @@ dependencies = [ "cfg-if 1.0.4", "once_cell", "rustversion", + "serde", "wasm-bindgen-macro", "wasm-bindgen-shared", ] @@ -13622,7 +13695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d967db7705dc29120bb6e8ce5b5a2e27734ed5976d1c904e95bd238d1c3c5a" dependencies = [ "bv", - "pastey", + "pastey 0.2.3", "proc-macro2", "quote", "thiserror 2.0.20", diff --git a/Cargo.toml b/Cargo.toml index 5c255b83c..e6cfaa79c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,8 @@ mustache = "0.9.0" notify = { version = "8.0.0", default-features = false } npm_rs = "1.0.0" once_cell = { version = "1.19.0", default-features = false } +phoenix-rise-accounts = "=0.3.4" +phoenix-rise-math = "=0.3.4" ratatui = { version = "0.29.0", features = [ "crossterm", ], default-features = false } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 2e7fe922a..3f7c76c97 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -114,6 +114,8 @@ opentelemetry_sdk = { version = "0.28", default-features = false, features = ["r opentelemetry-prometheus = { version = "0.28", default-features = false, optional = true } prometheus = { version = "0.13", default-features = false, optional = true } axum = { version = "0.8", default-features = false, features = ["tokio", "http1"], optional = true } +phoenix-rise-accounts = { workspace = true } +phoenix-rise-math = { workspace = true } [dev-dependencies] ed25519-dalek = "1.0.1" diff --git a/crates/core/src/scenarios/README.md b/crates/core/src/scenarios/README.md index 10950e578..032de3c81 100644 --- a/crates/core/src/scenarios/README.md +++ b/crates/core/src/scenarios/README.md @@ -19,6 +19,7 @@ Protocols that are natively supported by Surfpool will have their IDLs included - **Kamino v1.x** – Lending protocol with Reserve liquidity, risk config, and Obligation health override templates - **Drift v2** - Perp and spot markets, user state, and global state - **Pump v1** - Bonding curve launchpad with curve reserve and global config override templates +- **Phoenix Eternal** - Perpetuals venue with trader collateral, mark-price, and spot/perp reference override templates, built through its MCP tools - **PumpSwap v1** - Constant-product AMM with pool state and global config override templates, including canonical pool derivation for migrated pump.fun coins For custom protocols, an IDL can be registered at runtime using the [`surfnet_registerIdl`](https://docs.surfpool.run/rpc/cheatcodes#surfnet-registeridl) RPC cheatcode. diff --git a/crates/core/src/scenarios/examples/phoenix-eternal-liquidation-cascade.json b/crates/core/src/scenarios/examples/phoenix-eternal-liquidation-cascade.json new file mode 100644 index 000000000..a41a9085e --- /dev/null +++ b/crates/core/src/scenarios/examples/phoenix-eternal-liquidation-cascade.json @@ -0,0 +1,41 @@ +{ + "id": "phoenix-eternal-liquidation-cascade-example", + "name": "Phoenix Eternal Liquidation Cascade - BTC", + "description": "Stresses one Phoenix Eternal Trader's collateral, then shocks the BTC mark price a slot later, so the trader crosses into liquidation territory while the run is live. Replace the trader pubkey with an account that exists on your fork; collateral can only be lowered, since an override cannot mint the vault backing it. Both values are decimal strings because they span the full i64/u64 range.", + "tags": [ + "phoenix-eternal", + "liquidation", + "multi-slot" + ], + "overrides": [ + { + "id": "trader-collateral-stress", + "templateId": "phoenix-trader-collateral-stress", + "label": "Phoenix Trader collateral stress", + "scenarioRelativeSlot": 0, + "enabled": true, + "fetchBeforeUse": true, + "account": { + "pubkey": "" + }, + "values": { + "quote_lot_collateral": "900000000" + } + }, + { + "id": "btc-direct-mark-shock", + "templateId": "phoenix-direct-mark-risk-shock", + "label": "Phoenix BTC direct mark risk shock", + "scenarioRelativeSlot": 1, + "enabled": true, + "fetchBeforeUse": true, + "account": { + "pubkey": "2nHGAaEw3D5dd4hVueaUNoygkQFmoeKqRQWnSPqSMFUC" + }, + "values": { + "symbol": "BTC", + "target_ticks": "1" + } + } + ] +} diff --git a/crates/core/src/scenarios/protocols/mod.rs b/crates/core/src/scenarios/protocols/mod.rs index 99f0b0967..e378abba7 100644 --- a/crates/core/src/scenarios/protocols/mod.rs +++ b/crates/core/src/scenarios/protocols/mod.rs @@ -1 +1,3 @@ +#[path = "phoenix-eternal/mod.rs"] +pub mod phoenix_eternal; pub mod pump; diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md new file mode 100644 index 000000000..058616f00 --- /dev/null +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md @@ -0,0 +1,154 @@ +# Phoenix Eternal state preparation + +This integration prepares deterministic Phoenix Eternal account state. Bots remain responsible +for submitting trades, arbitrage, and liquidation transactions. + +## Supported state preparations + +| Goal | Template | State changed | +| --- | --- | --- | +| Collateral stress | `phoenix-trader-collateral-stress` | Exact signed quote-lot collateral on one validated Trader account | +| Direct mark shock | `phoenix-direct-mark-risk-shock` | Mark-price ticks for one market in the current PerpAssetMap | +| Spot/perp divergence | `phoenix-reference-price-divergence` | Cached spot and external-perp reference ticks while preserving the mark price | +| Liquidation cascade | Two validated overrides in one scenario | Trader collateral at slot 0, then a direct market mark shock at slot 1 | + +Tick inputs are Phoenix protocol ticks, not human-readable USD prices. Pass tick and collateral +values as decimal strings so values outside JavaScript's safe integer range remain exact. + +## Use from Studio + +1. Start an online Surfpool fork and open Studio. +2. Open **Scenario presets**, choose **Phoenix state**, then select the state goal. +3. For price scenarios, type the market symbol, such as `BTC`. For collateral stress, enter a + Phoenix Eternal Trader account. +4. Enter the target values and create the scenario. +5. Inspect the generated override, then press **Play** to activate it. +6. Send the bot, trade, arbitrage, or liquidation transaction you want to evaluate to the local + Surfnet RPC, normally `http://127.0.0.1:8899`. + +Play prepares account state; it does not submit a Phoenix transaction. The Transaction Inspector +remains empty until a client sends a transaction against the prepared state. + +## Where scenarios are built + +Phoenix has no HTTP routes of its own. The perp asset map address is a GlobalConfig field rather +than a PDA, but both market templates carry that address directly, so those two scenarios need no +tool. Only the collateral scenario needs one, because it must read and validate the live Trader +account first — the one tool listed below. Studio's preset calls that tool for collateral and posts +the market templates directly. + +Accounts are read through the Surfnet's own RPC, so local state wins and only missing accounts fall +back to the fork's datasource. A scenario therefore computes from the state you see, including +accounts you changed locally. + +## Use through MCP + +One tool, for the one scenario a template cannot express: + +| Tool | Required parameters | +| --- | --- | +| `create_phoenix_collateral_scenario` | `trader`, `targetQuoteLots` | + +It returns a Studio editor URL. The backend reads the live Trader account and refuses a target +the global vault does not back, which an LLM cannot bypass. + +The market scenarios need no tool: their templates carry the perp asset map address, so a client +fills in the values and posts the scenario to `/v1/scenarios`, which is what the Studio preset +does. The liquidation cascade is those two templates in one scenario at slots 0 and 1 — +[`phoenix-eternal-liquidation-cascade.json`](../../examples/phoenix-eternal-liquidation-cascade.json) +is a ready example to post or import. + +## What each preparation guarantees + +- Collateral stress changes only `quote_lot_collateral` in the selected Trader account. +- Direct mark shock changes only the selected market's mark-price ticks and the mark-price slot, + which is stamped with the slot the override materializes at so the program reads the new mark + as fresh. +- Reference divergence changes all five cached spot-reference ticks and all five cached + external-perp-reference ticks for the selected market, stamping their slots the same way. It + preserves the mark price, orderbook, spline liquidity, account length, and every unrelated byte. +- Liquidation cascade is the collateral and direct-mark overrides in one scenario: the collateral + override activates at slot 0 and the mark shock at slot 1. +- Collateral stress only lowers collateral when created through the MCP tool or the Studio + preset: the field is a claim on the global vault's real tokens, which an override cannot + create, so the builder refuses a raise; deposit first. A scenario posted straight to + `/v1/scenarios` bypasses that check by design — writers write what the scenario says — and + owns the consequences. +- Generated overrides leave `fetchBeforeUse` off: creation already read the accounts the plan + applies to, and a refresh at Play time would patch a different version of them. An override that + asks for a refresh anyway takes the shared refresh path, like every other protocol: the fork + serves the rest of the account graph lazily when the program reads it. + +These guarantees describe state preparation. Whether a particular transaction trades, arbitrages, +or liquidates depends on the transaction, the selected account, and the rest of the forked state. + +## Troubleshooting + +| Error or observation | Meaning | +| --- | --- | +| `Phoenix GlobalConfig ... was not found` or `Phoenix PerpAssetMap ... was not found` | Neither the local fork nor its datasource holds the Phoenix account graph. Start Surfpool against a datasource that carries the deployment. | +| `Phoenix market ... was not found` | The symbol is not in the live PerpAssetMap. Symbols are exact, such as `BTC`. | +| `Phoenix collateral stress can only lower collateral` | The target exceeds what the global vault backs. Send a real deposit to raise collateral. | +| `Expected a valid Phoenix Eternal Trader account` | The supplied account is not a decodable Phoenix Eternal Trader owned by the deployed program. | +| Scenario is green but the Transaction Inspector is empty | The state is active, but no client transaction has been sent yet. | +| A transaction does not produce the expected economic result | Confirm its accounts and instruction path consume the field changed by the selected preparation. | + +## Layout drift + +Phoenix is zero-copy, so decoding an account built by these tests can never disagree with the +decoder that built it. Drift is caught against live mainnet accounts instead: + +```sh +cargo test -p surfpool-core --features integration-tests tests::phoenix +``` + +Those tests resolve the live account graph, assert the typed invariants the program relies on, +and prove an override on a live account changes only its target bytes. Set +`SURFPOOL_TEST_RPC_URL` to use a private endpoint if the public one rate-limits. + +## Behavioral verification + +The behavioral test runs with the rest of the integration suite; there is nothing to install by +hand and no snapshots to keep current, since the deployed bytecode is fetched straight from the +chain: + +```sh +cargo test -p surfpool-core --features integration-tests tests::phoenix +``` + +It forks the live account graph, discovers a live Trader that carries collateral and a long +position, and loads the deployed Phoenix Eternal and Hawkeye bytecode straight from their +ProgramData accounts (`B5ayDaz9HegiNZqYeBtcFqfZBVSGwjB2CJgHshoSfMQg` and +`Gv1WgG864CQqF5vedJVbpnhpRpRbTW1A7SyARzSw9B4Y`), cached under the system temp directory as +`surfpool-phoenix-eternal.so` and `surfpool-phoenix-hawkeye.so`. Delete those files to pick up a +program upgrade; the cached bytecode is otherwise reused as-is. + +The raw material is selected, not arbitrary: it walks the program's Trader accounts for one that +carries collateral, and for the mark-shock and cascade tests, one that also holds a long position, +since a downward shock only threatens a long. The risk condition itself — a trader actually +falling below its maintenance requirement — is produced by the preparations, not found +pre-existing. The run fails loudly with `no eligible live candidate` rather than skipping if the +raw material is missing. It proves, through real Phoenix and Hawkeye execution: + +- collateral stress lands in the account the program reads, and lowers the collateral its risk + engine can count on; +- the two cascade stages arrive in order: the prepared collateral at the first slot, and the + mark the program reads shocked at the next; +- spot/perp divergence moves the cached reference away from the mark while the mark itself + stands; +- a second live market takes the same direct-mark preparation, so the templates are not + market-specific. + +Expected test summary: + +```text +test result: ok. 9 passed; 0 failed; 0 ignored +``` + +## Why there is no orderbook-consumption test + +An earlier version proved a bot could consume the prepared orderbook by signing a market sell as +the localnet fixture's taker. A live Trader's key is not ours to sign with, so that test went +with the fixture. What it covered is still covered: the market and spline accounts are never +written by any Phoenix preparation, which the byte-level assertions on live accounts enforce +directly, and Hawkeye reads the resulting book state in the behavioral test. diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/mod.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/mod.rs new file mode 100644 index 000000000..a3a6d96c3 --- /dev/null +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/mod.rs @@ -0,0 +1 @@ +pub mod v1; diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/editor-schema.json b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/editor-schema.json new file mode 100644 index 000000000..104546a01 --- /dev/null +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/editor-schema.json @@ -0,0 +1,74 @@ +{ + "address": "EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih", + "metadata": { + "name": "phoenix_eternal_write_mode_schema", + "version": "0.3.4", + "spec": "0.1.0", + "description": "Editor schema for specialized Phoenix scenario writers; not an account serialization contract" + }, + "instructions": [], + "accounts": [ + { + "name": "TraderCollateralOverride", + "discriminator": [41, 97, 73, 105, 110, 214, 112, 9] + }, + { + "name": "DirectMarkOverride", + "discriminator": [142, 54, 115, 223, 124, 239, 108, 38] + }, + { + "name": "ReferencePriceOverride", + "discriminator": [210, 123, 24, 82, 132, 17, 53, 94] + } + ], + "types": [ + { + "name": "TraderCollateralOverride", + "type": { + "kind": "struct", + "fields": [ + { + "name": "quote_lot_collateral", + "type": "string" + } + ] + } + }, + { + "name": "DirectMarkOverride", + "type": { + "kind": "struct", + "fields": [ + { + "name": "symbol", + "type": "string" + }, + { + "name": "target_ticks", + "type": "string" + } + ] + } + }, + { + "name": "ReferencePriceOverride", + "type": { + "kind": "struct", + "fields": [ + { + "name": "symbol", + "type": "string" + }, + { + "name": "spot_ticks", + "type": "string" + }, + { + "name": "perp_ticks", + "type": "string" + } + ] + } + } + ] +} diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/mod.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/mod.rs new file mode 100644 index 000000000..d7e8cec45 --- /dev/null +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/mod.rs @@ -0,0 +1 @@ +pub mod state_builder; diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml new file mode 100644 index 000000000..022181bfa --- /dev/null +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml @@ -0,0 +1,312 @@ +protocol: Phoenix Eternal +version: v1 +account_type: TraderCollateralOverride +idl_file_path: editor-schema.json + +tags: + - perpetuals + - collateral + - risk + +constants: + # The perp markets listed on the live PerpAssetMap (captured 2026-08-31). A newly + # listed market reaches this catalog on the next update; until then the raw + # scenario API accepts any symbol and the writer validates it against live state. + market_symbol: + label: Market + description: Select a Phoenix Eternal perp market + options: + - id: aapl + label: AAPL + value: AAPL + - id: aave + label: AAVE + value: AAVE + - id: ada + label: ADA + value: ADA + - id: amat + label: AMAT + value: AMAT + - id: amd + label: AMD + value: AMD + - id: amzn + label: AMZN + value: AMZN + - id: ansem + label: ANSEM + value: ANSEM + - id: arm + label: ARM + value: ARM + - id: asml + label: ASML + value: ASML + - id: avgo + label: AVGO + value: AVGO + - id: baba + label: BABA + value: BABA + - id: bnb + label: BNB + value: BNB + - id: btc + label: BTC + value: BTC + - id: cbrs + label: CBRS + value: CBRS + - id: chip + label: CHIP + value: CHIP + - id: coin + label: COIN + value: COIN + - id: copper + label: COPPER + value: COPPER + - id: crcl + label: CRCL + value: CRCL + - id: crv + label: CRV + value: CRV + - id: crwd + label: CRWD + value: CRWD + - id: crwv + label: CRWV + value: CRWV + - id: doge + label: DOGE + value: DOGE + - id: ena + label: ENA + value: ENA + - id: eth + label: ETH + value: ETH + - id: fartcoin + label: FARTCOIN + value: FARTCOIN + - id: fet + label: FET + value: FET + - id: gold + label: GOLD + value: GOLD + - id: googl + label: GOOGL + value: GOOGL + - id: hood + label: HOOD + value: HOOD + - id: hype + label: HYPE + value: HYPE + - id: intc + label: INTC + value: INTC + - id: iren + label: IREN + value: IREN + - id: jto + label: JTO + value: JTO + - id: jup + label: JUP + value: JUP + - id: link + label: LINK + value: LINK + - id: lit + label: LIT + value: LIT + - id: lly + label: LLY + value: LLY + - id: mega + label: MEGA + value: MEGA + - id: met + label: MET + value: MET + - id: meta + label: META + value: META + - id: mon + label: MON + value: MON + - id: morpho + label: MORPHO + value: MORPHO + - id: mrna + label: MRNA + value: MRNA + - id: mrvl + label: MRVL + value: MRVL + - id: msft + label: MSFT + value: MSFT + - id: mstr + label: MSTR + value: MSTR + - id: mu + label: MU + value: MU + - id: nbis + label: NBIS + value: NBIS + - id: near + label: NEAR + value: NEAR + - id: net + label: NET + value: NET + - id: nflx + label: NFLX + value: NFLX + - id: nvda + label: NVDA + value: NVDA + - id: ondo + label: ONDO + value: ONDO + - id: pltr + label: PLTR + value: PLTR + - id: pump + label: PUMP + value: PUMP + - id: qcom + label: QCOM + value: QCOM + - id: render + label: RENDER + value: RENDER + - id: silver + label: SILVER + value: SILVER + - id: skhy + label: SKHY + value: SKHY + - id: skr + label: SKR + value: SKR + - id: sndk + label: SNDK + value: SNDK + - id: sol + label: SOL + value: SOL + - id: spcx + label: SPCX + value: SPCX + - id: sui + label: SUI + value: SUI + - id: tao + label: TAO + value: TAO + - id: trx + label: TRX + value: TRX + - id: tsla + label: TSLA + value: TSLA + - id: tsm + label: TSM + value: TSM + - id: virtual + label: VIRTUAL + value: VIRTUAL + - id: vvv + label: VVV + value: VVV + - id: wld + label: WLD + value: WLD + - id: wtioil + label: WTIOIL + value: WTIOIL + - id: xlm + label: XLM + value: XLM + - id: xpl + label: XPL + value: XPL + - id: xrp + label: XRP + value: XRP + - id: zec + label: ZEC + value: ZEC + +templates: + - id: phoenix-trader-collateral-stress + name: Phoenix Trader Collateral Stress + description: Set exact signed quote-lot collateral on one validated Phoenix Eternal Trader account. + idl_account_name: TraderCollateralOverride + properties: + - path: quote_lot_collateral + label: Target Collateral in Quote Lots + description: Exact signed quote-lot collateral encoded as a decimal string + llm_context: | + This prepares collateral stress state. It does not guarantee liquidation and does not execute liquidation. + For a liquidation cascade, pair it with phoenix-direct-mark-risk-shock at the next slot. Keep override labels short. + Pass quote_lot_collateral as a decimal string. Builder-created scenarios leave fetchBeforeUse off; the value applies to the local fork state. + address: + type: pubkey + + - id: phoenix-direct-mark-risk-shock + name: Phoenix Direct Mark Risk Shock + description: Set exact mark-price ticks for one market in a validated Phoenix Eternal PerpAssetMap. + idl_account_name: DirectMarkOverride + properties: + - path: symbol + type: constant_ref + label: Market Symbol + constant: market_symbol + description: Exact Phoenix market symbol + - path: target_ticks + label: Target Mark Ticks + description: Exact unsigned mark-price ticks encoded as a decimal string + llm_context: | + This directly changes Phoenix risk state, setting the mark ticks and stamping the mark slot with the materialization slot. It does not simulate an oracle, orderbook, spline, or trade. + Pass target_ticks as a decimal string. The account is the singleton PerpAssetMap, so this template carries its address. + Set fetchBeforeUse: true so the live asset map is forked first; use false only for a later + override that builds on state an earlier one prepared in the same scenario. + # The singleton named by GlobalConfig's perp_asset_map field, which only moves if the + # protocol migrates the map. + address: + type: pubkey + value: 2nHGAaEw3D5dd4hVueaUNoygkQFmoeKqRQWnSPqSMFUC + + - id: phoenix-reference-price-divergence + name: Phoenix Spot/Perp Reference Divergence + description: Set independent spot and external-perp reference ticks while preserving the current mark price. + idl_account_name: ReferencePriceOverride + properties: + - path: symbol + type: constant_ref + label: Market Symbol + constant: market_symbol + description: Exact Phoenix market symbol + - path: spot_ticks + label: Spot Reference Ticks + description: Exact spot reference ticks encoded as a decimal string + - path: perp_ticks + label: Perp Reference Ticks + description: Exact external-perp reference ticks encoded as a decimal string + llm_context: | + This prepares spot/perp reference and mark divergence state for bots to act on, setting the cached reference ticks and stamping their slots with the materialization slot. + It preserves the current mark, orderbook, and spline liquidity and does not execute a trade. + Pass both tick values as decimal strings. The account is the singleton PerpAssetMap, so this template carries its address. + Set fetchBeforeUse: true so the live asset map is forked first; use false only for a later + override that builds on state an earlier one prepared in the same scenario. + address: + type: pubkey + value: 2nHGAaEw3D5dd4hVueaUNoygkQFmoeKqRQWnSPqSMFUC diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs new file mode 100644 index 000000000..8a65dece4 --- /dev/null +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs @@ -0,0 +1,1314 @@ +use core::mem::size_of; +use std::collections::HashMap; + +use phoenix_rise_accounts::{ + PhoenixAccountDecodeError, + global_config::GlobalConfig, + perp_asset_map::{ + MarkPrice, PerpAssetMap, PerpPriceComponent, PriceComponent, SpotPriceComponent, + TicksAtSlot, + }, + trader::{Trader, TraderHeader}, +}; +use phoenix_rise_math::quantities::{SignedQuoteLots, Ticks}; +use solana_account::Account; +use solana_pubkey::Pubkey; +use surfpool_types::{AccountAddress, OverrideInstance, Scenario}; +use thiserror::Error; + +use crate::{ + error::{SurfpoolError, SurfpoolResult}, + scenarios::TemplateRegistry, +}; + +pub const PHOENIX_ETERNAL_PROGRAM_ID: Pubkey = + Pubkey::from_str_const("EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih"); +pub const PHOENIX_GLOBAL_CONFIG: Pubkey = + Pubkey::from_str_const("2zskx2iyCvb6Stg7RBZkt1f6MrF4dpYtMG3yMvKwqtUZ"); + +const TRADER_HEADER_LEN: usize = size_of::(); +const COLLATERAL_BYTE_RANGE: core::ops::Range = 88..96; +const COLLATERAL_FIELD: &str = "quote_lot_collateral"; +const DIRECT_MARK_SYMBOL_FIELD: &str = "symbol"; +const DIRECT_MARK_TICKS_FIELD: &str = "target_ticks"; +const REFERENCE_SPOT_TICKS_FIELD: &str = "spot_ticks"; +const REFERENCE_PERP_TICKS_FIELD: &str = "perp_ticks"; +const PREPARATION_SLOT: u64 = 0; +const MARK_PRICE_RANGE: core::ops::Range = 16..32; +const MARK_PRICE_TICKS_RANGE: core::ops::Range = 24..32; + +#[derive(Clone, Debug, PartialEq)] +pub struct PhoenixCollateralPreparation { + pub scenario: Scenario, + pub trader: Pubkey, + pub target_quote_lots: i64, +} + +pub fn phoenix_market_symbols( + perp_asset_map: Pubkey, + account: &Account, +) -> SurfpoolResult> { + if account.owner != PHOENIX_ETERNAL_PROGRAM_ID { + return Err(SurfpoolError::invalid_account_owner( + perp_asset_map, + None::, + )); + } + + let map = PerpAssetMap::try_from_account_bytes(&account.data).map_err(|error| { + SurfpoolError::invalid_account_data( + perp_asset_map, + "Expected a valid Phoenix Eternal PerpAssetMap account", + Some(error), + ) + })?; + let mut symbols = map + .iter() + .map(|entry| entry.map(|entry| entry.symbol.as_str().to_string())) + .collect::, _>>() + .map_err(|error| { + SurfpoolError::invalid_account_data( + perp_asset_map, + "Expected a valid Phoenix Eternal PerpAssetMap account", + Some(error), + ) + })?; + symbols.sort_unstable(); + + Ok(symbols) +} + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum PhoenixTraderPatchError { + #[error("expected Phoenix Eternal owner, got {actual}")] + InvalidOwner { actual: Pubkey }, + #[error("invalid Phoenix Trader account: {0}")] + InvalidTrader(#[from] PhoenixAccountDecodeError), + #[error("collateral patch changed byte {offset} outside the collateral field")] + UnexpectedByteChange { offset: usize }, +} + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum PhoenixDirectMarkPatchError { + #[error("expected Phoenix Eternal owner, got {actual}")] + InvalidOwner { actual: Pubkey }, + #[error("invalid Phoenix PerpAssetMap account: {0}")] + InvalidPerpAssetMap(#[from] PhoenixAccountDecodeError), + #[error("Phoenix market {symbol} was not found")] + MarketNotFound { symbol: String }, + #[error("target mark ticks {ticks} exceed the Phoenix u32 tick range")] + InvalidTicks { ticks: u64 }, + #[error("selected Phoenix market metadata does not occur exactly once")] + InvalidMetadataLocation, + #[error("direct mark patch changed byte {offset} outside the mark tick field")] + UnexpectedByteChange { offset: usize }, +} + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum PhoenixReferencePricePatchError { + #[error("expected Phoenix Eternal owner, got {actual}")] + InvalidOwner { actual: Pubkey }, + #[error("invalid Phoenix PerpAssetMap account: {0}")] + InvalidPerpAssetMap(#[from] PhoenixAccountDecodeError), + #[error("Phoenix market {symbol} was not found")] + MarketNotFound { symbol: String }, + #[error("reference ticks {ticks} exceed the Phoenix u32 tick range")] + InvalidTicks { ticks: u64 }, + #[error("selected Phoenix market metadata does not occur exactly once")] + InvalidMetadataLocation, + #[error("reference-price patch changed byte {offset} outside the spot/perp fields")] + UnexpectedByteChange { offset: usize }, +} + +/// The offset of the first byte a patch changed outside the ranges it was +/// allowed to touch, if there is one. +fn changed_byte_outside( + original: &[u8], + patched: &[u8], + allowed: &[core::ops::Range], +) -> Option { + original + .iter() + .zip(patched) + .enumerate() + .find(|(offset, (before, after))| { + before != after && !allowed.iter().any(|range| range.contains(offset)) + }) + .map(|(offset, _)| offset) +} + +pub fn patch_trader_collateral( + owner: &Pubkey, + data: &[u8], + target_quote_lots: i64, +) -> Result, PhoenixTraderPatchError> { + if owner != &PHOENIX_ETERNAL_PROGRAM_ID { + return Err(PhoenixTraderPatchError::InvalidOwner { actual: *owner }); + } + + validate_trader(data)?; + + let mut header = TraderHeader::try_read_from_account_bytes(data)?; + header.trader_state.quote_lot_collateral = SignedQuoteLots::new(target_quote_lots); + + let mut patched_data = data.to_vec(); + patched_data[..TRADER_HEADER_LEN].copy_from_slice(bytemuck::bytes_of(&header)); + + validate_trader(&patched_data)?; + + if let Some(offset) = changed_byte_outside(data, &patched_data, &[COLLATERAL_BYTE_RANGE]) { + return Err(PhoenixTraderPatchError::UnexpectedByteChange { offset }); + } + + Ok(patched_data) +} + +pub fn patch_direct_mark( + owner: &Pubkey, + data: &[u8], + symbol: &str, + target_ticks: u64, +) -> Result, PhoenixDirectMarkPatchError> { + patch_direct_mark_inner(owner, data, symbol, target_ticks, None) +} + +fn patch_direct_mark_at_slot( + owner: &Pubkey, + data: &[u8], + symbol: &str, + target_ticks: u64, + mark_slot: u64, +) -> Result, PhoenixDirectMarkPatchError> { + patch_direct_mark_inner(owner, data, symbol, target_ticks, Some(mark_slot)) +} + +fn patch_direct_mark_inner( + owner: &Pubkey, + data: &[u8], + symbol: &str, + target_ticks: u64, + mark_slot: Option, +) -> Result, PhoenixDirectMarkPatchError> { + if owner != &PHOENIX_ETERNAL_PROGRAM_ID { + return Err(PhoenixDirectMarkPatchError::InvalidOwner { actual: *owner }); + } + let target_ticks = Ticks::new_checked(target_ticks).map_err(|_| { + PhoenixDirectMarkPatchError::InvalidTicks { + ticks: target_ticks, + } + })?; + let map = PerpAssetMap::try_from_account_bytes(data)?; + let entry = + map.find_by_symbol(symbol)? + .ok_or_else(|| PhoenixDirectMarkPatchError::MarketNotFound { + symbol: symbol.to_string(), + })?; + let metadata_bytes = entry.metadata.as_bytes(); + let metadata_offset = unique_subslice_offset(data, metadata_bytes) + .ok_or(PhoenixDirectMarkPatchError::InvalidMetadataLocation)?; + let price_len = size_of::(); + let mut price = bytemuck::pod_read_unaligned::(&metadata_bytes[..price_len]); + if let Some(mark_slot) = mark_slot { + price.mark_price.price.slot = mark_slot; + } + price.mark_price.price.ticks = target_ticks; + + let mut patched_data = data.to_vec(); + patched_data[metadata_offset..metadata_offset + price_len] + .copy_from_slice(bytemuck::bytes_of(&price)); + PerpAssetMap::try_from_account_bytes(&patched_data)?; + + let mark_range = if mark_slot.is_some() { + MARK_PRICE_RANGE + } else { + MARK_PRICE_TICKS_RANGE + }; + let allowed_range = metadata_offset + mark_range.start..metadata_offset + mark_range.end; + if let Some(offset) = changed_byte_outside(data, &patched_data, &[allowed_range]) { + return Err(PhoenixDirectMarkPatchError::UnexpectedByteChange { offset }); + } + + Ok(patched_data) +} + +pub fn patch_reference_prices( + owner: &Pubkey, + data: &[u8], + symbol: &str, + spot_ticks: u64, + perp_ticks: u64, +) -> Result, PhoenixReferencePricePatchError> { + patch_reference_prices_inner(owner, data, symbol, spot_ticks, perp_ticks, None) +} + +fn patch_reference_prices_at_slot( + owner: &Pubkey, + data: &[u8], + symbol: &str, + spot_ticks: u64, + perp_ticks: u64, + reference_slot: u64, +) -> Result, PhoenixReferencePricePatchError> { + patch_reference_prices_inner( + owner, + data, + symbol, + spot_ticks, + perp_ticks, + Some(reference_slot), + ) +} + +fn patch_reference_prices_inner( + owner: &Pubkey, + data: &[u8], + symbol: &str, + spot_ticks: u64, + perp_ticks: u64, + reference_slot: Option, +) -> Result, PhoenixReferencePricePatchError> { + if owner != &PHOENIX_ETERNAL_PROGRAM_ID { + return Err(PhoenixReferencePricePatchError::InvalidOwner { actual: *owner }); + } + let spot_ticks = Ticks::new_checked(spot_ticks) + .map_err(|_| PhoenixReferencePricePatchError::InvalidTicks { ticks: spot_ticks })?; + let perp_ticks = Ticks::new_checked(perp_ticks) + .map_err(|_| PhoenixReferencePricePatchError::InvalidTicks { ticks: perp_ticks })?; + let map = PerpAssetMap::try_from_account_bytes(data)?; + let entry = map.find_by_symbol(symbol)?.ok_or_else(|| { + PhoenixReferencePricePatchError::MarketNotFound { + symbol: symbol.to_string(), + } + })?; + let metadata_bytes = entry.metadata.as_bytes(); + let metadata_offset = unique_subslice_offset(data, metadata_bytes) + .ok_or(PhoenixReferencePricePatchError::InvalidMetadataLocation)?; + let price_len = size_of::(); + let mut price = bytemuck::pod_read_unaligned::(&metadata_bytes[..price_len]); + for value in &mut price + .mark_price + .spot_price_component + .last_exchange_spot_price + { + if let Some(reference_slot) = reference_slot { + value.slot = reference_slot; + } + value.ticks = spot_ticks; + } + for value in &mut price + .mark_price + .perp_price_component + .last_exchange_perp_price + { + if let Some(reference_slot) = reference_slot { + value.slot = reference_slot; + } + value.ticks = perp_ticks; + } + + let mut patched_data = data.to_vec(); + patched_data[metadata_offset..metadata_offset + price_len] + .copy_from_slice(bytemuck::bytes_of(&price)); + PerpAssetMap::try_from_account_bytes(&patched_data)?; + + let allowed_ranges = reference_value_ranges(metadata_offset, reference_slot.is_some()); + if let Some(offset) = changed_byte_outside(data, &patched_data, &allowed_ranges) { + return Err(PhoenixReferencePricePatchError::UnexpectedByteChange { offset }); + } + + Ok(patched_data) +} + +fn reference_value_ranges( + metadata_offset: usize, + include_slots: bool, +) -> Vec> { + let mark_offset = core::mem::offset_of!(PriceComponent, mark_price); + let ticks_offset = core::mem::offset_of!(TicksAtSlot, ticks); + let spot_offset = mark_offset + + core::mem::offset_of!(MarkPrice, spot_price_component) + + core::mem::offset_of!(SpotPriceComponent, last_exchange_spot_price); + let perp_offset = mark_offset + + core::mem::offset_of!(MarkPrice, perp_price_component) + + core::mem::offset_of!(PerpPriceComponent, last_exchange_perp_price); + [spot_offset, perp_offset] + .into_iter() + .flat_map(|component_offset| { + (0..5).map(move |index| { + let value_start = + metadata_offset + component_offset + index * size_of::(); + if include_slots { + value_start..value_start + size_of::() + } else { + let ticks_start = value_start + ticks_offset; + ticks_start..ticks_start + size_of::() + } + }) + }) + .collect() +} + +fn unique_subslice_offset(data: &[u8], needle: &[u8]) -> Option { + if needle.is_empty() || needle.len() > data.len() { + return None; + } + + let mut match_offset = None; + let mut search_start = 0; + while search_start + needle.len() <= data.len() { + let Some(relative_offset) = data[search_start..=data.len() - needle.len()] + .iter() + .position(|byte| *byte == needle[0]) + else { + break; + }; + let offset = search_start + relative_offset; + if data[offset..].starts_with(needle) { + if match_offset.is_some() { + return None; + } + match_offset = Some(offset); + } + search_start = offset + 1; + } + match_offset +} + +/// Routes a Phoenix-owned account's override to the right typed patcher by the +/// value group it carries; the patchers themselves enforce the account type. +pub fn forge_phoenix_override( + account_pubkey: &Pubkey, + account: &Account, + account_values: &HashMap, + materialization_slot: u64, +) -> SurfpoolResult> { + let wants_collateral = account_values.contains_key(COLLATERAL_FIELD); + let wants_direct_mark = account_values.contains_key(DIRECT_MARK_TICKS_FIELD); + let wants_reference = account_values.contains_key(REFERENCE_SPOT_TICKS_FIELD) + || account_values.contains_key(REFERENCE_PERP_TICKS_FIELD); + + match (wants_collateral, wants_direct_mark, wants_reference) { + (true, false, false) => { + forge_trader_collateral_override(account_pubkey, account, account_values) + } + (false, true, false) => forge_direct_mark_override( + account_pubkey, + account, + account_values, + materialization_slot, + ), + (false, false, true) => forge_reference_price_override( + account_pubkey, + account, + account_values, + materialization_slot, + ), + _ => Err(SurfpoolError::internal( + "Phoenix overrides accept exactly one value group: quote_lot_collateral, \ + symbol + target_ticks, or symbol + spot_ticks + perp_ticks", + )), + } +} + +pub fn forge_trader_collateral_override( + account_pubkey: &Pubkey, + account: &Account, + account_values: &HashMap, +) -> SurfpoolResult> { + if account_values.len() != 1 || !account_values.contains_key(COLLATERAL_FIELD) { + return Err(SurfpoolError::internal( + "Phoenix Trader collateral overrides accept only quote_lot_collateral", + )); + } + + let target_quote_lots = account_values[COLLATERAL_FIELD] + .as_str() + .ok_or_else(invalid_quote_lot_collateral) + .and_then(parse_quote_lot_collateral)?; + + patch_trader_collateral(&account.owner, &account.data, target_quote_lots).map_err(|error| { + match error { + PhoenixTraderPatchError::InvalidOwner { .. } => { + SurfpoolError::invalid_account_owner(account_pubkey, Some(error)) + } + _ => SurfpoolError::invalid_account_data( + account_pubkey, + "Expected a valid Phoenix Eternal Trader account", + Some(error), + ), + } + }) +} + +pub fn forge_direct_mark_override( + account_pubkey: &Pubkey, + account: &Account, + account_values: &HashMap, + materialization_slot: u64, +) -> SurfpoolResult> { + if account_values.len() != 2 + || !account_values.contains_key(DIRECT_MARK_SYMBOL_FIELD) + || !account_values.contains_key(DIRECT_MARK_TICKS_FIELD) + { + return Err(SurfpoolError::internal( + "Phoenix direct mark overrides accept only symbol and target_ticks", + )); + } + + let symbol = account_values[DIRECT_MARK_SYMBOL_FIELD] + .as_str() + .filter(|value| !value.is_empty()) + .ok_or_else(|| SurfpoolError::internal("symbol must be a non-empty string"))?; + let target_ticks = account_values[DIRECT_MARK_TICKS_FIELD] + .as_str() + .and_then(|value| value.parse::().ok()) + .ok_or_else(|| { + SurfpoolError::internal( + "target_ticks must be an unsigned 64-bit integer encoded as a string", + ) + })?; + + patch_direct_mark_at_slot( + &account.owner, + &account.data, + symbol, + target_ticks, + materialization_slot, + ) + .map_err(|error| match error { + PhoenixDirectMarkPatchError::InvalidOwner { .. } => { + SurfpoolError::invalid_account_owner(account_pubkey, Some(error)) + } + _ => SurfpoolError::invalid_account_data( + account_pubkey, + "Expected a valid Phoenix Eternal PerpAssetMap account", + Some(error), + ), + }) +} + +pub fn forge_reference_price_override( + account_pubkey: &Pubkey, + account: &Account, + account_values: &HashMap, + materialization_slot: u64, +) -> SurfpoolResult> { + if account_values.len() != 3 + || !account_values.contains_key(DIRECT_MARK_SYMBOL_FIELD) + || !account_values.contains_key(REFERENCE_SPOT_TICKS_FIELD) + || !account_values.contains_key(REFERENCE_PERP_TICKS_FIELD) + { + return Err(SurfpoolError::internal( + "Phoenix reference-price overrides accept only symbol, spot_ticks, and perp_ticks", + )); + } + + let symbol = account_values[DIRECT_MARK_SYMBOL_FIELD] + .as_str() + .filter(|value| !value.is_empty()) + .ok_or_else(|| SurfpoolError::internal("symbol must be a non-empty string"))?; + let spot_ticks = parse_unsigned_ticks( + &account_values[REFERENCE_SPOT_TICKS_FIELD], + REFERENCE_SPOT_TICKS_FIELD, + )?; + let perp_ticks = parse_unsigned_ticks( + &account_values[REFERENCE_PERP_TICKS_FIELD], + REFERENCE_PERP_TICKS_FIELD, + )?; + + patch_reference_prices_at_slot( + &account.owner, + &account.data, + symbol, + spot_ticks, + perp_ticks, + materialization_slot, + ) + .map_err(|error| match error { + PhoenixReferencePricePatchError::InvalidOwner { .. } => { + SurfpoolError::invalid_account_owner(account_pubkey, Some(error)) + } + _ => SurfpoolError::invalid_account_data( + account_pubkey, + "Expected a valid Phoenix Eternal PerpAssetMap account", + Some(error), + ), + }) +} + +pub fn build_phoenix_collateral_scenario( + trader: Pubkey, + trader_account: &Account, + target_quote_lots: &str, +) -> SurfpoolResult { + let target_quote_lots = parse_quote_lot_collateral(target_quote_lots)?; + patch_trader_collateral( + &trader_account.owner, + &trader_account.data, + target_quote_lots, + ) + .map_err(|error| match error { + PhoenixTraderPatchError::InvalidOwner { .. } => { + SurfpoolError::invalid_account_owner(trader, Some(error)) + } + _ => SurfpoolError::invalid_account_data( + trader, + "Expected a valid Phoenix Eternal Trader account", + Some(error), + ), + })?; + + // Collateral is a claim on the global vault's real tokens. An override cannot + // mint that backing, so raising it would let withdrawals draw on balances that + // are not there; a real DepositFunds transaction is the way up. + let current_quote_lots = TraderHeader::try_read_from_account_bytes(&trader_account.data) + .map(|header| header.trader_state.quote_lot_collateral.as_inner()) + .map_err(|error| { + SurfpoolError::invalid_account_data( + trader, + "Expected a valid Phoenix Eternal Trader account", + Some(error), + ) + })?; + if target_quote_lots > current_quote_lots { + return Err(SurfpoolError::internal(format!( + "Phoenix collateral stress can only lower collateral: {current_quote_lots} quote lots \ + are backed by the global vault, {target_quote_lots} would not be. Deposit first to \ + raise it." + ))); + } + + let template = TemplateRegistry::new() + .get("phoenix-trader-collateral-stress") + .cloned() + .ok_or_else(|| SurfpoolError::internal("Phoenix collateral template is unavailable"))?; + let values = HashMap::from([( + COLLATERAL_FIELD.to_string(), + serde_json::json!(target_quote_lots.to_string()), + )]); + let collateral_override = OverrideInstance::new( + template.id, + PREPARATION_SLOT, + AccountAddress::Pubkey(trader.to_string()), + ) + .with_values(values) + .with_label("Phoenix Trader collateral stress".to_string()); + + let mut scenario = Scenario::new( + "Phoenix Trader Collateral Stress".to_string(), + "Set exact signed quote-lot collateral on a validated Phoenix Eternal Trader account." + .to_string(), + ); + scenario.tags = vec![ + "phoenix-eternal".to_string(), + "collateral".to_string(), + "risk".to_string(), + ]; + scenario.add_override(collateral_override); + + Ok(PhoenixCollateralPreparation { + scenario, + trader, + target_quote_lots, + }) +} + +pub fn phoenix_perp_asset_map_address(global_account: &Account) -> SurfpoolResult { + if global_account.owner != PHOENIX_ETERNAL_PROGRAM_ID { + return Err(SurfpoolError::invalid_account_owner( + PHOENIX_GLOBAL_CONFIG, + Some("expected Phoenix Eternal owner"), + )); + } + let global = GlobalConfig::try_from_account_bytes(&global_account.data).map_err(|error| { + SurfpoolError::invalid_account_data( + PHOENIX_GLOBAL_CONFIG, + "Expected a valid Phoenix Eternal GlobalConfig account", + Some(error), + ) + })?; + if Pubkey::new_from_array(global.account_key()) != PHOENIX_GLOBAL_CONFIG { + return Err(SurfpoolError::invalid_account_data( + PHOENIX_GLOBAL_CONFIG, + "GlobalConfig account_key does not match its address", + None::, + )); + } + Ok(Pubkey::new_from_array(global.perp_asset_map_key())) +} + +fn parse_unsigned_ticks(value: &serde_json::Value, field: &str) -> SurfpoolResult { + value + .as_str() + .and_then(|value| value.parse::().ok()) + .ok_or_else(|| { + SurfpoolError::internal(format!( + "{field} must be an unsigned 64-bit integer encoded as a string" + )) + }) +} + +fn parse_quote_lot_collateral(value: &str) -> SurfpoolResult { + value + .parse::() + .map_err(|_| invalid_quote_lot_collateral()) +} + +fn invalid_quote_lot_collateral() -> SurfpoolError { + SurfpoolError::internal( + "quote_lot_collateral must be a signed 64-bit integer encoded as a string", + ) +} + +fn validate_trader(data: &[u8]) -> Result<(), PhoenixAccountDecodeError> { + let mut aligned_words = vec![0_u64; data.len().div_ceil(size_of::())]; + let aligned_bytes = bytemuck::cast_slice_mut::(&mut aligned_words); + aligned_bytes[..data.len()].copy_from_slice(data); + Trader::try_from_account_bytes(&aligned_bytes[..data.len()])?; + Ok(()) +} + +#[cfg(test)] +pub(crate) mod tests { + use base64::{Engine, prelude::BASE64_STANDARD}; + use phoenix_rise_accounts::{PhoenixAccount, trader::TraderHeader}; + use solana_account::Account; + + use super::*; + + const POSITION_MAP_PREFIX_LEN: usize = 16; + const POSITION_ENTRY_LEN: usize = 40; + const PERP_ASSET_MAP_LEN: usize = 1_622_064; + const SOL_PERP_ASSET_MAP_PREFIX_B64: &str = "jjZz33zvbCYBAAAAAAAAAF+FshYAAAAALAAAAAAAAAAtAAAAAQAAAAAEAAAAAAAAU09MAAAAAAAAAAAAAAAAAJIjVQMAAAAAK+yGGQAAAAAr7IYZAAAAABccAAAAAAAAK+yGGQAAAAAXHAAAAAAAACXshhkAAAAAFxwAAAAAAAAk7IYZAAAAABccAAAAAAAAIuyGGQAAAAAWHAAAAAAAACnshhkAAAAAGBwAAAAAAABkAAAAAAAAABkAAAAAAAAAK+yGGQAAAAAAAAAAAAAAAHUAAAAAAAAAdwEAAAAAAAByAQAAAAAAACvshhkAAAAAERwAAAAAAAAl7IYZAAAAABEcAAAAAAAAJOyGGQAAAAASHAAAAAAAACLshhkAAAAAERwAAAAAAAAp7IYZAAAAABEcAAAAAAAAZAAAAAAAAAAZAAAAAAAAACvshhkAAAAAGRwAAAAAAABkAAAAAAAAAGQAAAAAAAAAcgEAAAAAAAAk7IYZAAAAABkcAAAAAAAAJOyGGQAAAAAaHAAAAAAAAPjrhhkAAAAAFBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAQAAAAAAAgIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgIBAAAAAAACAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgEAAAAAAAICAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAQAAAAAAAgIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgIBAAAAAAACAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2d/uHkzTMEI+nE0Ymaus9KEPf4oJXVEtWcWP29rQOywAAAAAAAAAAPBv/oFQVyUzn/BwmYuYTfvalmqearF4UMH8Xu10jPi1AAAAAAAAAAC9eIxYdxEuqtIqoFaGCUmDIS3Ki2887zwxOIzii37LZgAAAAAAAAAAp5Qc5gqxc5w9o5gk0/YHpMTClPTT8zaXjCVPspfSfxwAAAAAAAAAAIj2IrJxxvwcSeH0Zi3/xWcn5icVCYuh/OncuwHqSRBjAAAAAAAAAAD0AQEAAAAAACvshhkAAAAAnI6GGQAAAAAAAAAAAAAAAFRyhhkAAAAA5wAF8p4BAAAh9gTyngEAAFj1BPKeAQAAxu8E8p4BAAC6/ATyngEAAAAAAAAAAAAAAAAAAAAAAABZQzBUxbJLqOqoIX/f+QNvuZxLwZEZqXGqSDPsYEwjH2QAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATMvqAQAAAAAPAAAAAAAAABAnAAAAAAAATcvqAQAAAAABAAAAAAAAABAnAAAAAAAATsvqAQAAAAABAAAAAAAAABAnAAAAAAAAT8vqAQAAAAABAAAAAAAAABAnAAAAAAAAECcAAAAAAABQwwAAAAAAAKCGAQAAAAAAZAAAAAAAAAAgoQcAAAAAAMgAAAAAAAAAQEIPAAAAAAAsAQAAAAAAAICWmAAAAAAAkAEAAAAAAACIE9AH6ANMHWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCQAAAAAAAJDaOWoAAAAAatw5agAAAAAQDgAAAAAAAIBRAQAAAAAAogYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnuYhAAAAAABMy+oBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQlRDAAAAAAAAAAAAAAAAAA=="; + + fn trader_fixture(collateral: i64, len: u64, capacity: u64) -> Vec { + let capacity = usize::try_from(capacity).expect("fixture capacity"); + let mut data = + vec![0_u8; TRADER_HEADER_LEN + POSITION_MAP_PREFIX_LEN + capacity * POSITION_ENTRY_LEN]; + data[..8].copy_from_slice(&PhoenixAccount::Trader.discriminant()); + data[COLLATERAL_BYTE_RANGE].copy_from_slice(&collateral.to_le_bytes()); + data[112..116].copy_from_slice(&(capacity as u32).to_le_bytes()); + data[TRADER_HEADER_LEN..TRADER_HEADER_LEN + 8].copy_from_slice(&len.to_le_bytes()); + data[TRADER_HEADER_LEN + 8..TRADER_HEADER_LEN + 16] + .copy_from_slice(&(capacity as u64).to_le_bytes()); + if len > 0 && capacity > 0 { + data[TRADER_HEADER_LEN + POSITION_MAP_PREFIX_LEN + ..TRADER_HEADER_LEN + POSITION_MAP_PREFIX_LEN + 8] + .copy_from_slice(&42_u64.to_le_bytes()); + } + data + } + + fn trader_account() -> Account { + Account { + lamports: 1, + data: trader_fixture(0, 1, 2), + owner: PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + } + } + + pub(crate) fn perp_asset_map_fixture() -> Vec { + let prefix = BASE64_STANDARD + .decode(SOL_PERP_ASSET_MAP_PREFIX_B64) + .unwrap(); + let mut data = vec![0_u8; PERP_ASSET_MAP_LEN]; + data[..prefix.len()].copy_from_slice(&prefix); + data[24..26].copy_from_slice(&1_u16.to_le_bytes()); + data[32..36].copy_from_slice(&1_u32.to_le_bytes()); + data[36..40].copy_from_slice(&0_u32.to_le_bytes()); + data + } + + #[test] + fn patches_only_collateral_and_preserves_the_dynamic_tail() { + let data = trader_fixture(6_996_825_500, 1, 2); + let dynamic_tail = data[TRADER_HEADER_LEN..].to_vec(); + + let patched = patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data, 371_499_999) + .expect("valid collateral patch"); + + let header = TraderHeader::try_read_from_account_bytes(&patched).expect("valid header"); + assert_eq!( + header.trader_state.quote_lot_collateral.as_inner(), + 371_499_999 + ); + assert_eq!(patched.len(), data.len()); + assert_eq!(&patched[TRADER_HEADER_LEN..], dynamic_tail); + assert!( + data.iter() + .zip(&patched) + .enumerate() + .filter(|(_, (before, after))| before != after) + .all(|(offset, _)| COLLATERAL_BYTE_RANGE.contains(&offset)) + ); + } + + #[test] + fn accepts_i64_collateral_boundaries() { + let data = trader_fixture(0, 0, 0); + + for target in [i64::MIN, i64::MAX] { + let patched = + patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data, target).unwrap(); + let header = TraderHeader::try_read_from_account_bytes(&patched).unwrap(); + assert_eq!(header.trader_state.quote_lot_collateral.as_inner(), target); + } + } + + #[test] + fn rejects_the_wrong_owner() { + let error = patch_trader_collateral(&Pubkey::new_unique(), &trader_fixture(0, 0, 0), 1) + .unwrap_err(); + + assert!(matches!( + error, + PhoenixTraderPatchError::InvalidOwner { .. } + )); + } + + #[test] + fn rejects_the_wrong_discriminant() { + let mut data = trader_fixture(0, 0, 0); + data[..8].fill(0); + + let error = patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data, 1).unwrap_err(); + + assert!(matches!( + error, + PhoenixTraderPatchError::InvalidTrader( + PhoenixAccountDecodeError::InvalidDiscriminant { .. } + ) + )); + } + + #[test] + fn rejects_a_truncated_header() { + let data = trader_fixture(0, 0, 0); + + let error = patch_trader_collateral( + &PHOENIX_ETERNAL_PROGRAM_ID, + &data[..TRADER_HEADER_LEN - 1], + 1, + ) + .unwrap_err(); + + assert!(matches!( + error, + PhoenixTraderPatchError::InvalidTrader( + PhoenixAccountDecodeError::AccountTooSmall { .. } + ) + )); + } + + #[test] + fn rejects_missing_allocated_position_capacity() { + let data = trader_fixture(0, 1, 2); + let truncated_len = data.len() - POSITION_ENTRY_LEN; + + let error = patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data[..truncated_len], 1) + .unwrap_err(); + + assert!(matches!( + error, + PhoenixTraderPatchError::InvalidTrader( + PhoenixAccountDecodeError::AccountTooSmall { .. } + ) + )); + } + + #[test] + fn rejects_position_length_above_capacity() { + let data = trader_fixture(2, 2, 1); + + let error = patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data, 1).unwrap_err(); + + assert!(matches!( + error, + PhoenixTraderPatchError::InvalidTrader(PhoenixAccountDecodeError::InvalidData { + reason: "position map length exceeds capacity", + .. + }) + )); + } + + #[test] + fn override_accepts_only_the_collateral_field() { + let values = HashMap::from([ + (COLLATERAL_FIELD.to_string(), serde_json::json!("371499999")), + ("flags".to_string(), serde_json::json!("1")), + ]); + + let error = + forge_trader_collateral_override(&Pubkey::new_unique(), &trader_account(), &values) + .unwrap_err(); + + assert!(error.to_string().contains("accept only")); + } + + #[test] + fn override_rejects_json_numbers() { + let values = + HashMap::from([(COLLATERAL_FIELD.to_string(), serde_json::json!(371_499_999))]); + + let error = + forge_trader_collateral_override(&Pubkey::new_unique(), &trader_account(), &values) + .unwrap_err(); + + assert!(error.to_string().contains("encoded as a string")); + } + + #[test] + fn override_rejects_out_of_range_strings() { + let values = HashMap::from([( + COLLATERAL_FIELD.to_string(), + serde_json::json!("9223372036854775808"), + )]); + + let error = + forge_trader_collateral_override(&Pubkey::new_unique(), &trader_account(), &values) + .unwrap_err(); + + assert!(error.to_string().contains("signed 64-bit integer")); + } + + #[test] + fn override_parses_exact_string_values() { + let values = HashMap::from([( + COLLATERAL_FIELD.to_string(), + serde_json::json!("-9007199254740993"), + )]); + + let patched = + forge_trader_collateral_override(&Pubkey::new_unique(), &trader_account(), &values) + .unwrap(); + let header = TraderHeader::try_read_from_account_bytes(&patched).unwrap(); + + assert_eq!( + header.trader_state.quote_lot_collateral.as_inner(), + -9_007_199_254_740_993 + ); + } + + #[test] + fn builds_one_editable_collateral_override_for_the_requested_trader() { + let trader = Pubkey::new_unique(); + let preparation = + build_phoenix_collateral_scenario(trader, &trader_account(), "-9007199254740993") + .unwrap(); + + assert_eq!(preparation.trader, trader); + assert_eq!(preparation.target_quote_lots, -9_007_199_254_740_993); + assert_eq!(preparation.scenario.overrides.len(), 1); + + let collateral_override = &preparation.scenario.overrides[0]; + assert_eq!( + collateral_override.template_id, + "phoenix-trader-collateral-stress" + ); + assert_eq!( + collateral_override.account, + AccountAddress::Pubkey(trader.to_string()) + ); + assert_eq!( + collateral_override.values[COLLATERAL_FIELD], + serde_json::json!("-9007199254740993") + ); + assert_eq!(collateral_override.scenario_relative_slot, PREPARATION_SLOT); + assert!(!collateral_override.fetch_before_use); + } + + #[test] + fn builder_rejects_an_invalid_target_before_creating_a_scenario() { + let error = build_phoenix_collateral_scenario( + Pubkey::new_unique(), + &trader_account(), + "9223372036854775808", + ) + .unwrap_err(); + + assert!(error.to_string().contains("signed 64-bit integer")); + } + + #[test] + fn builder_refuses_to_raise_collateral_above_its_vault_backing() { + let trader = Pubkey::new_unique(); + let funded = Account { + data: trader_fixture(500, 1, 2), + ..trader_account() + }; + + let raised = build_phoenix_collateral_scenario(trader, &funded, "501").unwrap_err(); + assert!(raised.to_string().contains("can only lower collateral")); + + let lowered = build_phoenix_collateral_scenario(trader, &funded, "499").unwrap(); + assert_eq!(lowered.target_quote_lots, 499); + let held = build_phoenix_collateral_scenario(trader, &funded, "500").unwrap(); + assert_eq!(held.target_quote_lots, 500); + } + + #[test] + fn builder_rejects_a_non_phoenix_account() { + let trader = Pubkey::new_unique(); + let mut account = trader_account(); + account.owner = Pubkey::new_unique(); + + let error = build_phoenix_collateral_scenario(trader, &account, "1").unwrap_err(); + + assert!(error.to_string().contains("invalid account owner")); + } + + #[test] + fn lists_active_market_symbols_from_the_perp_asset_map() { + let perp_asset_map = Pubkey::new_unique(); + let account = Account { + lamports: 1, + data: perp_asset_map_fixture(), + owner: PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }; + + assert_eq!( + phoenix_market_symbols(perp_asset_map, &account).unwrap(), + vec!["SOL"] + ); + } + + #[test] + fn patches_only_the_selected_mark_ticks_in_an_official_market_entry() { + let data = perp_asset_map_fixture(); + let map = PerpAssetMap::try_from_account_bytes(&data).unwrap(); + let before = map.find_by_symbol("SOL").unwrap().unwrap(); + let metadata_offset = unique_subslice_offset(&data, before.metadata.as_bytes()).unwrap(); + + let patched = patch_direct_mark(&PHOENIX_ETERNAL_PROGRAM_ID, &data, "SOL", 1).unwrap(); + let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); + let after = map.find_by_symbol("SOL").unwrap().unwrap(); + + assert_eq!( + after + .metadata + .oracle_price() + .mark_price + .price + .ticks + .as_inner(), + 1 + ); + assert_eq!(patched.len(), data.len()); + assert!( + data.iter() + .zip(&patched) + .enumerate() + .filter(|(_, (before, after))| before != after) + .all(|(offset, _)| { + (metadata_offset + MARK_PRICE_TICKS_RANGE.start + ..metadata_offset + MARK_PRICE_TICKS_RANGE.end) + .contains(&offset) + }) + ); + } + + #[test] + fn reference_price_patch_supports_both_divergence_directions_and_preserves_mark() { + let data = perp_asset_map_fixture(); + let before_map = PerpAssetMap::try_from_account_bytes(&data).unwrap(); + let before = before_map.find_by_symbol("SOL").unwrap().unwrap(); + let before_mark = before + .metadata + .oracle_price() + .mark_price + .price + .ticks + .as_inner(); + + for (spot_ticks, perp_ticks) in [(8_000, 7_000), (7_000, 8_000)] { + let patched = patch_reference_prices( + &PHOENIX_ETERNAL_PROGRAM_ID, + &data, + "SOL", + spot_ticks, + perp_ticks, + ) + .unwrap(); + let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); + let entry = map.find_by_symbol("SOL").unwrap().unwrap(); + let price = entry.metadata.oracle_price(); + + assert_eq!(price.mark_price.price.ticks.as_inner(), before_mark); + assert!( + price + .mark_price + .spot_price_component + .last_exchange_spot_price + .iter() + .all(|value| value.ticks.as_inner() == spot_ticks) + ); + assert!( + price + .mark_price + .perp_price_component + .last_exchange_perp_price + .iter() + .all(|value| value.ticks.as_inner() == perp_ticks) + ); + assert_eq!(patched.len(), data.len()); + } + } + + #[test] + fn reference_price_patch_refreshes_each_reference_slot() { + let data = perp_asset_map_fixture(); + let patched = patch_reference_prices_at_slot( + &PHOENIX_ETERNAL_PROGRAM_ID, + &data, + "SOL", + 8_000, + 7_000, + 123, + ) + .unwrap(); + let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); + let entry = map.find_by_symbol("SOL").unwrap().unwrap(); + let price = entry.metadata.oracle_price(); + + assert!( + price + .mark_price + .spot_price_component + .last_exchange_spot_price + .iter() + .all(|value| value.slot == 123 && value.ticks.as_inner() == 8_000) + ); + assert!( + price + .mark_price + .perp_price_component + .last_exchange_perp_price + .iter() + .all(|value| value.slot == 123 && value.ticks.as_inner() == 7_000) + ); + } + + #[test] + fn reference_price_override_requires_exact_string_fields() { + let account = Account { + lamports: 1, + data: perp_asset_map_fixture(), + owner: PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }; + let values = HashMap::from([ + ( + DIRECT_MARK_SYMBOL_FIELD.to_string(), + serde_json::json!("SOL"), + ), + ( + REFERENCE_SPOT_TICKS_FIELD.to_string(), + serde_json::json!("8000"), + ), + ( + REFERENCE_PERP_TICKS_FIELD.to_string(), + serde_json::json!("7000"), + ), + ]); + forge_reference_price_override(&Pubkey::new_unique(), &account, &values, 100).unwrap(); + + let mut numeric = values; + numeric.insert( + REFERENCE_SPOT_TICKS_FIELD.to_string(), + serde_json::json!(8000), + ); + assert!( + forge_reference_price_override(&Pubkey::new_unique(), &account, &numeric, 100) + .unwrap_err() + .to_string() + .contains("encoded as a string") + ); + } + + #[test] + fn direct_mark_rejects_unknown_markets_and_out_of_range_ticks() { + let data = perp_asset_map_fixture(); + + assert!(matches!( + patch_direct_mark(&PHOENIX_ETERNAL_PROGRAM_ID, &data, "BTC", 1), + Err(PhoenixDirectMarkPatchError::MarketNotFound { .. }) + )); + assert!(matches!( + patch_direct_mark( + &PHOENIX_ETERNAL_PROGRAM_ID, + &data, + "SOL", + u64::from(u32::MAX) + 1, + ), + Err(PhoenixDirectMarkPatchError::InvalidTicks { .. }) + )); + } + + #[test] + fn direct_mark_override_requires_exact_string_fields() { + let account = Account { + lamports: 1, + data: perp_asset_map_fixture(), + owner: PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }; + let values = HashMap::from([ + ( + DIRECT_MARK_SYMBOL_FIELD.to_string(), + serde_json::json!("SOL"), + ), + (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), + ]); + + let patched = + forge_direct_mark_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); + let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); + let price = map + .find_by_symbol("SOL") + .unwrap() + .unwrap() + .metadata + .oracle_price() + .mark_price + .price; + assert_eq!(price.ticks.as_inner(), 1); + assert_eq!(price.slot, 123); + + let mut numeric_ticks = values; + numeric_ticks.insert(DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!(1)); + assert!( + forge_direct_mark_override(&Pubkey::new_unique(), &account, &numeric_ticks, 123) + .unwrap_err() + .to_string() + .contains("encoded as a string") + ); + } + + #[test] + fn forge_dispatches_collateral_values_to_the_trader_patcher() { + let values = + HashMap::from([(COLLATERAL_FIELD.to_string(), serde_json::json!("371499999"))]); + + let patched = + forge_phoenix_override(&Pubkey::new_unique(), &trader_account(), &values, 100).unwrap(); + let header = TraderHeader::try_read_from_account_bytes(&patched).unwrap(); + + assert_eq!( + header.trader_state.quote_lot_collateral.as_inner(), + 371_499_999 + ); + } + + #[test] + fn forge_dispatches_symbol_and_target_ticks_to_the_direct_mark_patcher() { + let account = Account { + lamports: 1, + data: perp_asset_map_fixture(), + owner: PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }; + let values = HashMap::from([ + ( + DIRECT_MARK_SYMBOL_FIELD.to_string(), + serde_json::json!("SOL"), + ), + (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), + ]); + + let patched = + forge_phoenix_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); + let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); + let price = map + .find_by_symbol("SOL") + .unwrap() + .unwrap() + .metadata + .oracle_price() + .mark_price + .price; + + assert_eq!(price.ticks.as_inner(), 1); + assert_eq!(price.slot, 123); + } + + #[test] + fn forge_dispatches_symbol_and_reference_ticks_to_the_reference_patcher() { + let account = Account { + lamports: 1, + data: perp_asset_map_fixture(), + owner: PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }; + let values = HashMap::from([ + ( + DIRECT_MARK_SYMBOL_FIELD.to_string(), + serde_json::json!("SOL"), + ), + ( + REFERENCE_SPOT_TICKS_FIELD.to_string(), + serde_json::json!("8000"), + ), + ( + REFERENCE_PERP_TICKS_FIELD.to_string(), + serde_json::json!("7000"), + ), + ]); + + let patched = + forge_phoenix_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); + let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); + let entry = map.find_by_symbol("SOL").unwrap().unwrap(); + let price = entry.metadata.oracle_price(); + + assert!( + price + .mark_price + .spot_price_component + .last_exchange_spot_price + .iter() + .all(|value| value.slot == 123 && value.ticks.as_inner() == 8_000) + ); + assert!( + price + .mark_price + .perp_price_component + .last_exchange_perp_price + .iter() + .all(|value| value.slot == 123 && value.ticks.as_inner() == 7_000) + ); + } + + #[test] + fn forge_rejects_no_value_group() { + let error = forge_phoenix_override( + &Pubkey::new_unique(), + &trader_account(), + &HashMap::new(), + 100, + ) + .unwrap_err(); + + assert!(error.to_string().contains("exactly one value group")); + } + + #[test] + fn forge_rejects_mixed_value_groups() { + let values = HashMap::from([ + (COLLATERAL_FIELD.to_string(), serde_json::json!("371499999")), + (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), + ]); + + let error = forge_phoenix_override(&Pubkey::new_unique(), &trader_account(), &values, 100) + .unwrap_err(); + + assert!(error.to_string().contains("exactly one value group")); + } +} diff --git a/crates/core/src/scenarios/registry.rs b/crates/core/src/scenarios/registry.rs index c426d3777..f89c70778 100644 --- a/crates/core/src/scenarios/registry.rs +++ b/crates/core/src/scenarios/registry.rs @@ -39,6 +39,11 @@ pub const PUMP_AMM_V1_IDL_CONTENT: &str = include_str!("./protocols/pump-amm/v1/ pub const PUMP_AMM_V1_OVERRIDES_CONTENT: &str = include_str!("./protocols/pump-amm/v1/overrides.yaml"); +pub const PHOENIX_ETERNAL_IDL_CONTENT: &str = + include_str!("./protocols/phoenix-eternal/v1/editor-schema.json"); +pub const PHOENIX_ETERNAL_OVERRIDES_CONTENT: &str = + include_str!("./protocols/phoenix-eternal/v1/overrides.yaml"); + /// Registry for managing override templates loaded from YAML files #[derive(Clone, Debug, Default)] pub struct TemplateRegistry { @@ -59,6 +64,7 @@ impl TemplateRegistry { default.load_whirlpool_overrides(); default.load_spl_token_overrides(); default.load_pump_overrides(); + default.load_phoenix_overrides(); default } @@ -128,6 +134,14 @@ impl TemplateRegistry { ); } + pub fn load_phoenix_overrides(&mut self) { + self.load_protocol_overrides( + PHOENIX_ETERNAL_IDL_CONTENT, + PHOENIX_ETERNAL_OVERRIDES_CONTENT, + "phoenix-eternal", + ); + } + fn load_protocol_overrides( &mut self, idl_content: &str, @@ -426,11 +440,11 @@ mod tests { fn test_registry_loads_all_protocols() { let registry = TemplateRegistry::new(); - // Should have Pyth (1 template) + Jupiter (1) + Raydium CLMM (1) + Raydium AMM v4 (4) + Drift(4) + Meteora (2) + Kamino(3) + Whirlpool(6) + SPL Token (2) + Pump (2) + PumpSwap (3) = 29 total + // Should have Pyth (1 template) + Jupiter (1) + Raydium CLMM (1) + Raydium AMM v4 (4) + Drift(4) + Meteora (2) + Kamino(3) + Whirlpool(6) + SPL Token (2) + Pump (2) + PumpSwap (3) + Phoenix Eternal (3) = 32 total assert_eq!( registry.count(), - 29, - "Registry should load 29 templates total" + 32, + "Registry should load 32 templates total" ); assert!(registry.contains("pyth-price-feed-v2")); @@ -472,6 +486,9 @@ mod tests { assert!(registry.contains("pump-amm-pool-state")); assert!(registry.contains("pump-amm-canonical-pool")); assert!(registry.contains("pump-amm-global-config")); + + assert!(registry.contains("phoenix-trader-collateral-stress")); + assert!(registry.contains("phoenix-direct-mark-risk-shock")); } #[test] @@ -532,6 +549,13 @@ mod tests { 3, "Should have 3 PumpSwap templates" ); + + let phoenix_templates = registry.by_protocol("Phoenix Eternal"); + assert_eq!( + phoenix_templates.len(), + 3, + "Should have 3 Phoenix Eternal templates" + ); } #[test] diff --git a/crates/core/src/surfnet/svm.rs b/crates/core/src/surfnet/svm.rs index 1b715f8a7..e7f561f98 100644 --- a/crates/core/src/surfnet/svm.rs +++ b/crates/core/src/surfnet/svm.rs @@ -91,7 +91,12 @@ use super::{ use crate::{ error::{AirdropError, SurfpoolError, SurfpoolResult}, rpc::utils::convert_transaction_metadata_from_canonical, - scenarios::TemplateRegistry, + scenarios::{ + TemplateRegistry, + protocols::phoenix_eternal::v1::state_builder::{ + PHOENIX_ETERNAL_PROGRAM_ID, forge_phoenix_override, + }, + }, storage::{OverlayStorage, Storage, StorageBackend}, surfnet::{ LogsSubscriptionData, locker::is_supported_token_program, surfnet_lite_svm::SurfnetLiteSvm, @@ -2760,6 +2765,10 @@ impl SurfnetSvm { target_slot ); + // Accounts already patched in this batch. A later fetch-before-use override on the + // same account must not reinstall remote bytes over an earlier same-slot patch. + let mut patched_this_slot: HashSet = HashSet::new(); + for override_instance in overrides { if !override_instance.enabled { debug!("Skipping disabled override: {}", override_instance.id); @@ -2798,7 +2807,12 @@ impl SurfnetSvm { ); // Fetch fresh account data from remote if requested - if override_instance.fetch_before_use { + if override_instance.fetch_before_use && patched_this_slot.contains(&account_pubkey) { + debug!( + "Skipping refresh for {}: an earlier override in this slot already patched it", + account_pubkey + ); + } else if override_instance.fetch_before_use { if let Some((client, _)) = remote_ctx { debug!( "Fetching fresh account data for {} from remote", @@ -2935,10 +2949,32 @@ impl SurfnetSvm { rent_epoch: account.rent_epoch(), }; self.inner.set_account(account_pubkey, modified_account)?; + patched_this_slot.insert(account_pubkey); continue; } } + // Phoenix Eternal accounts are zero-copy: route them by owner through + // the typed codec, the same seam the token path uses. + if account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID { + let new_account_data = forge_phoenix_override( + &account_pubkey, + &account, + &account_values, + target_slot, + )?; + let modified_account = Account { + lamports: account.lamports(), + data: new_account_data, + owner: *account.owner(), + executable: account.executable(), + rent_epoch: account.rent_epoch(), + }; + self.inner.set_account(account_pubkey, modified_account)?; + patched_this_slot.insert(account_pubkey); + continue; + } + // Get the account owner (program ID) let owner_program_id = account.owner(); @@ -3021,6 +3057,7 @@ impl SurfnetSvm { account_pubkey, e ); } else { + patched_this_slot.insert(account_pubkey); debug!( "Successfully applied {} override(s) to account {} (override {})", override_instance.values.len(), @@ -4568,6 +4605,83 @@ mod tests { ); } + /// Two overrides on the same account in the same slot: the later one asks for a + /// refresh, which must not reinstall remote bytes over the earlier patch. + #[tokio::test(flavor = "multi_thread")] + async fn test_fetch_before_use_preserves_an_earlier_same_slot_patch() { + let url = canned_rpc(CANNED_TOKEN_ACCOUNT).await; + let remote = (SurfnetRemoteClient::new(url), CommitmentConfig::confirmed()); + let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + let locker = crate::surfnet::locker::SurfnetSvmLocker::new(svm); + let target = Pubkey::new_unique(); + + // The local account's lamports are the marker: a remote reinstall would replace them. + let local = crate::types::TokenAccount::new( + &spl_token_interface::id(), + Pubkey::new_unique(), + Pubkey::default(), + None, + ); + locker.with_svm_writer(|svm_writer| { + svm_writer + .set_account( + &target, + Account { + lamports: 1_000_000, + data: local.pack_into_vec(), + owner: spl_token_interface::id(), + executable: false, + rent_epoch: 0, + }, + ) + .unwrap(); + }); + + let mut scenario = surfpool_types::Scenario::new( + "same-slot patches".to_string(), + "a later refresh must not erase an earlier same-slot patch".to_string(), + ); + let first = surfpool_types::OverrideInstance::new( + "spl-token-account-balance".to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(target.to_string()), + ) + .with_values(HashMap::from([( + "amount".to_string(), + serde_json::json!("42"), + )])); + scenario.add_override(first); + let mut second = surfpool_types::OverrideInstance::new( + "spl-token-account-balance".to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(target.to_string()), + ) + .with_values(HashMap::from([( + "amount".to_string(), + serde_json::json!("77"), + )])); + second.fetch_before_use = true; + scenario.add_override(second); + + locker.register_scenario(scenario, Some(100)).unwrap(); + locker + .materialize_overrides_for_slot(&Some(remote), 100) + .await + .unwrap(); + + let after = locker + .with_svm_reader(|svm_reader| svm_reader.get_account(&target)) + .unwrap() + .unwrap(); + assert_eq!( + after.lamports, 1_000_000, + "the second override's refresh must be skipped: this account was already patched \ + in the same slot" + ); + let token = crate::types::TokenAccount::unpack(&after.data).unwrap(); + assert_eq!(token.amount(), 77); + } + fn build_transfer_transaction( payer: &Keypair, recipient: &Pubkey, diff --git a/crates/core/src/tests/live.rs b/crates/core/src/tests/live.rs new file mode 100644 index 000000000..853cb2b77 --- /dev/null +++ b/crates/core/src/tests/live.rs @@ -0,0 +1,60 @@ +//! Shared plumbing for tests that read mainnet. +//! +//! Set `SURFPOOL_TEST_RPC_URL` to use a private endpoint if the public one rate-limits. + +use solana_account::Account; +use solana_commitment_config::CommitmentConfig; +use solana_pubkey::Pubkey; + +use crate::surfnet::remote::SurfnetRemoteClient; + +pub const RPC_URL_ENV: &str = "SURFPOOL_TEST_RPC_URL"; +pub const DEFAULT_RPC_URL: &str = "https://api.mainnet-beta.solana.com"; + +pub fn client() -> SurfnetRemoteClient { + SurfnetRemoteClient::new( + std::env::var(RPC_URL_ENV).unwrap_or_else(|_| DEFAULT_RPC_URL.to_string()), + ) +} + +/// Fetches the accounts in one request, so every account returned is from the same slot. +pub async fn fetch(addresses: &[Pubkey]) -> Vec { + // The public endpoint throttles and intermittently 503s, which has nothing to do with what + // the callers assert. Retry a few times with backoff so a transient refusal is not read as a + // failure. + let mut attempt = 0; + let results = loop { + match client() + .get_multiple_accounts(addresses, CommitmentConfig::confirmed()) + .await + { + Ok(results) => break results, + Err(error) if attempt < 4 => { + attempt += 1; + tokio::time::sleep(std::time::Duration::from_millis(500 * attempt)).await; + let _ = error; + } + Err(error) => panic!("failed to fetch {addresses:?} from mainnet: {error}"), + } + }; + + results + .into_iter() + .zip(addresses) + .map(|(result, address)| { + result.map_account().unwrap_or_else(|_| { + panic!("{address} no longer exists on mainnet; the integration needs a new address") + }) + }) + .collect() +} + +/// The offsets at which two buffers differ. +pub fn diff_indices(left: &[u8], right: &[u8]) -> Vec { + left.iter() + .zip(right) + .enumerate() + .filter(|(_, (a, b))| a != b) + .map(|(index, _)| index) + .collect() +} diff --git a/crates/core/src/tests/mod.rs b/crates/core/src/tests/mod.rs index 54093ec1e..d18810e84 100644 --- a/crates/core/src/tests/mod.rs +++ b/crates/core/src/tests/mod.rs @@ -1,5 +1,9 @@ pub mod helpers; pub mod integration; +#[cfg(feature = "integration-tests")] +pub mod live; +#[cfg(feature = "integration-tests")] +pub mod phoenix; pub mod plugin; #[cfg(feature = "integration-tests")] pub mod pump; diff --git a/crates/core/src/tests/phoenix/mod.rs b/crates/core/src/tests/phoenix/mod.rs new file mode 100644 index 000000000..27e8e2acd --- /dev/null +++ b/crates/core/src/tests/phoenix/mod.rs @@ -0,0 +1,1093 @@ +//! Phoenix Eternal integration tests. +//! +//! These fetch the real accounts from mainnet rather than embedding captured copies, so they +//! need a network connection and are compiled only behind a feature: +//! +//! ```text +//! cargo test -p surfpool-core --features integration-tests phoenix +//! ``` +//! +//! Set `SURFPOOL_TEST_RPC_URL` to use a private endpoint if the public one rate-limits. +//! +//! What these cover that unit tests cannot: Phoenix is zero-copy, so decoding a synthetic +//! account and decoding the bytes it was built from can never disagree. Only live accounts +//! carry the real header values, populated market entries and the dynamic tail, so a program +//! upgrade that moves a field shows up here as a failed invariant or a stray byte diff. +//! +//! Nothing here hunts for a market that happens to sit in an interesting state: the live +//! accounts are the raw material, and the production builders prepare the scene. + +use phoenix_rise_accounts::{ + PhoenixAccount, + global_config::GlobalConfig, + pda::derive_spline_collection_address, + perp_asset_map::PerpAssetMap, + trader::{Trader, TraderHeader}, +}; +use std::collections::HashMap; + +use bytemuck::{Pod, Zeroable}; +use solana_account::Account; +use solana_account_decoder::UiAccountEncoding; +use solana_clock::Clock; +use solana_commitment_config::CommitmentConfig; +use solana_compute_budget_interface::ComputeBudgetInstruction; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_rpc_client_api::{ + config::RpcAccountInfoConfig, + filter::{Memcmp, RpcFilterType}, +}; +use solana_signer::Signer; +use solana_transaction::Transaction; + +use surfpool_types::AccountAddress; + +use crate::{ + scenarios::{ + TemplateRegistry, + protocols::phoenix_eternal::v1::state_builder::{ + PHOENIX_ETERNAL_PROGRAM_ID, PHOENIX_GLOBAL_CONFIG, build_phoenix_collateral_scenario, + patch_direct_mark, patch_reference_prices, patch_trader_collateral, + phoenix_market_symbols, phoenix_perp_asset_map_address, + }, + }, + surfnet::{locker::SurfnetSvmLocker, svm::SurfnetSvm}, + tests::live::{RPC_URL_ENV, client, diff_indices, fetch}, + types::RemoteRpcResult, +}; + +type LiveMarketGraph = (Account, Pubkey, Account, String); + +/// One fetch of the fork state per process. The perp asset map alone is 1.6MB, and running the +/// tests in parallel against a public endpoint is what exhausts it. +fn market_graph_cache() -> &'static tokio::sync::Mutex> { + static CACHE: std::sync::OnceLock>> = + std::sync::OnceLock::new(); + CACHE.get_or_init(|| tokio::sync::Mutex::new(None)) +} + +fn trader_cache() -> &'static tokio::sync::Mutex> { + static CACHE: std::sync::OnceLock>> = + std::sync::OnceLock::new(); + CACHE.get_or_init(|| tokio::sync::Mutex::new(HashMap::new())) +} + +/// The live Phoenix account graph: GlobalConfig, the perp asset map it points at, and the +/// symbol of a market that is actually listed right now. +async fn live_market_graph() -> LiveMarketGraph { + let mut cache = market_graph_cache().lock().await; + if let Some(cached) = cache.as_ref() { + return cached.clone(); + } + let global_account = fetch(&[PHOENIX_GLOBAL_CONFIG]).await.remove(0); + assert_eq!( + global_account.owner, PHOENIX_ETERNAL_PROGRAM_ID, + "GlobalConfig must be owned by the deployed Eternal program" + ); + + let perp_asset_map = phoenix_perp_asset_map_address(&global_account) + .expect("live GlobalConfig should resolve its perp asset map"); + let map_account = fetch(&[perp_asset_map]).await.remove(0); + let symbols = phoenix_market_symbols(perp_asset_map, &map_account) + .expect("live PerpAssetMap should decode"); + let symbol = symbols + .first() + .cloned() + .expect("no eligible live candidate: the Phoenix PerpAssetMap lists no markets"); + + let graph = (global_account, perp_asset_map, map_account, symbol); + *cache = Some(graph.clone()); + + graph +} + +/// A live Trader account with collateral on it. Traders are per-user accounts that come and go, +/// so the test discovers one through the program's own account list rather than pinning an +/// address that may be closed tomorrow. +pub(crate) async fn live_trader_with_position() -> (Pubkey, Account) { + live_candidate(true).await +} + +async fn live_trader() -> (Pubkey, Account) { + live_candidate(false).await +} + +/// Walks the program's own account list for a Trader that carries collateral, and an open +/// position when the caller needs something for a mark shock to act on. +async fn live_candidate(needs_position: bool) -> (Pubkey, Account) { + let mut cache = trader_cache().lock().await; + if let Some(hit) = cache.get(&needs_position) { + return hit.clone(); + } + let listed = client() + .get_program_accounts( + &PHOENIX_ETERNAL_PROGRAM_ID, + RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + commitment: Some(CommitmentConfig::confirmed()), + ..RpcAccountInfoConfig::default() + }, + Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( + 0, + &PhoenixAccount::Trader.discriminant(), + ))]), + ) + .await; + let candidates = match listed { + Ok(RemoteRpcResult::Ok(accounts)) => accounts, + // The protocol keeps its own trader index, but 0.3.4 exposes only the arena + // metadata, so the program's account list is the reader we have. + Ok(RemoteRpcResult::MethodNotSupported) => panic!( + "environment: this endpoint does not support getProgramAccounts, which these tests \ + need to find a live trader. Set {RPC_URL_ENV} to an endpoint that supports it. \ + Nothing is proven or disproven about the integration." + ), + Err(error) => panic!("failed to list live Phoenix traders: {error}"), + }; + + // Addresses come back in pubkey order, so a handful from the front is not a + // representative sample; scan in batches until one qualifies. + for batch in candidates.chunks(100).take(4) { + let pubkeys = batch.iter().map(|(pubkey, _)| *pubkey).collect::>(); + for (pubkey, account) in pubkeys.iter().zip(fetch(&pubkeys).await) { + let Ok(trader) = Trader::try_from_account_bytes(&account.data) else { + continue; + }; + let has_collateral = trader.header.trader_state.quote_lot_collateral.as_inner() > 0; + // A downward mark shock only threatens a long, so the risk scenarios need one: + // the shock direction is fixed, the trader is what we go looking for. + let holds_a_long = trader + .positions() + .any(|(_, position)| position.base_lot_position().as_inner() > 0); + if has_collateral && (!needs_position || holds_a_long) { + cache.insert(needs_position, (*pubkey, account.clone())); + return (*pubkey, account); + } + } + } + + panic!( + "no eligible live candidate: no live Phoenix Trader read carries collateral{}", + if needs_position { + " and a long position" + } else { + "" + } + ) +} + +/// A zero-copy layout cannot be round-tripped against itself, so drift shows up as an +/// invariant that stops holding on live bytes. +#[tokio::test(flavor = "multi_thread")] +async fn live_accounts_satisfy_the_typed_layout_invariants() { + let (global_account, perp_asset_map, map_account, symbol) = live_market_graph().await; + + let global = GlobalConfig::try_from_account_bytes(&global_account.data) + .expect("live GlobalConfig should decode through phoenix-rise-accounts"); + assert_eq!( + Pubkey::new_from_array(global.account_key()), + PHOENIX_GLOBAL_CONFIG, + "GlobalConfig stores its own address, so a moved field shows up here first" + ); + assert_eq!( + Pubkey::new_from_array(global.perp_asset_map_key()), + perp_asset_map + ); + assert_ne!( + Pubkey::new_from_array(global.global_trader_index_header_key()), + Pubkey::default() + ); + assert_ne!( + Pubkey::new_from_array(global.active_trader_buffer_header_key()), + Pubkey::default() + ); + + assert_eq!(map_account.owner, PHOENIX_ETERNAL_PROGRAM_ID); + let map = PerpAssetMap::try_from_account_bytes(&map_account.data) + .expect("live PerpAssetMap should decode through phoenix-rise-accounts"); + let entry = map + .find_by_symbol(&symbol) + .expect("symbol lookup should decode") + .expect("the symbol came from this map"); + let market = Pubkey::new_from_array(entry.metadata.static_market_params().market_account); + assert_ne!( + market, + Pubkey::default(), + "a listed market needs an address" + ); + + let price = entry.metadata.oracle_price(); + assert!( + price.mark_price.price.ticks.as_inner() > 0, + "{symbol} is listed with a zero mark price, which the risk engine cannot use" + ); + + // The spline address is derived, so a change in the seeds surfaces as an account the + // program would no longer find. + let spline = derive_spline_collection_address(&PHOENIX_ETERNAL_PROGRAM_ID, &market); + let spline_account = fetch(&[spline]).await.remove(0); + assert_eq!( + spline_account.owner, PHOENIX_ETERNAL_PROGRAM_ID, + "the derived spline collection must belong to the Eternal program" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn overrides_on_live_accounts_touch_only_their_target_bytes() { + let (_global_account, perp_asset_map, map_account, symbol) = live_market_graph().await; + + let map = PerpAssetMap::try_from_account_bytes(&map_account.data).expect("live map decodes"); + let entry = map + .find_by_symbol(&symbol) + .expect("symbol lookup") + .expect("listed symbol"); + let live_mark = entry + .metadata + .oracle_price() + .mark_price + .price + .ticks + .as_inner(); + + let shocked = patch_direct_mark( + &map_account.owner, + &map_account.data, + &symbol, + live_mark / 2 + 1, + ) + .expect("direct mark patch on the live map"); + assert_eq!( + shocked.len(), + map_account.data.len(), + "the map's dynamic tail must survive" + ); + let mark_diffs = diff_indices(&shocked, &map_account.data); + assert!( + !mark_diffs.is_empty() && mark_diffs.len() <= 16, + "a mark shock writes one tick field, got {} changed bytes", + mark_diffs.len() + ); + + let diverged = patch_reference_prices( + &map_account.owner, + &map_account.data, + &symbol, + live_mark * 2, + live_mark * 3, + ) + .expect("reference price patch on the live map"); + assert_eq!(diverged.len(), map_account.data.len()); + let reference_diffs = diff_indices(&diverged, &map_account.data); + assert!( + reference_diffs + .iter() + .all(|index| !mark_diffs.contains(index)), + "reference divergence must preserve the mark price it diverges from" + ); + + let _ = perp_asset_map; +} + +#[tokio::test(flavor = "multi_thread")] +async fn the_market_templates_address_the_live_perp_asset_map() { + let (_global_account, perp_asset_map, _map_account, _symbol) = live_market_graph().await; + let registry = TemplateRegistry::new(); + + for template_id in [ + "phoenix-direct-mark-risk-shock", + "phoenix-reference-price-divergence", + ] { + let template = registry + .get(template_id) + .unwrap_or_else(|| panic!("{template_id} should be registered")); + let addressed = match &template.address { + AccountAddress::Pubkey(value) => Pubkey::from_str_const(value), + other => panic!("{template_id} should address a fixed pubkey, got {other:?}"), + }; + assert_eq!( + addressed, perp_asset_map, + "{template_id} writes to a hardcoded map; a Phoenix migration moves the one \ + GlobalConfig points at, and nothing else here would notice" + ); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn collateral_stress_refuses_to_outrun_the_live_vault() { + let (trader, account) = live_trader().await; + let live_collateral = TraderHeader::try_read_from_account_bytes(&account.data) + .expect("a discovered trader decodes") + .trader_state + .quote_lot_collateral + .as_inner(); + + let lowered = build_phoenix_collateral_scenario(trader, &account, "1") + .expect("lowering collateral is state preparation"); + assert_eq!(lowered.target_quote_lots, 1); + + let raised = + build_phoenix_collateral_scenario(trader, &account, &(live_collateral + 1).to_string()) + .unwrap_err(); + assert!( + raised.to_string().contains("can only lower collateral"), + "raising collateral past its vault backing must be refused, got: {raised}" + ); + + let patched = patch_trader_collateral(&account.owner, &account.data, 1) + .expect("collateral patch on a valid trader"); + let diffs = diff_indices(&patched, &account.data); + assert!( + diffs.iter().all(|index| (88..96).contains(index)), + "only the collateral field may change, got {diffs:?}" + ); +} + +const HAWKEYE_VIEW_MARGIN_DISCRIMINANT: [u8; 8] = [0xb2, 0x0a, 0x7c, 0xad, 0xec, 0xd2, 0x75, 0x06]; +const HAWKEYE_VIEW_BBO_DISCRIMINANT: [u8; 8] = [0x37, 0x5f, 0x23, 0x2d, 0x53, 0xaf, 0x12, 0x52]; +const ETERNAL_PROGRAMDATA: Pubkey = + Pubkey::from_str_const("B5ayDaz9HegiNZqYeBtcFqfZBVSGwjB2CJgHshoSfMQg"); +const HAWKEYE_PROGRAMDATA: Pubkey = + Pubkey::from_str_const("Gv1WgG864CQqF5vedJVbpnhpRpRbTW1A7SyARzSw9B4Y"); + +/// The deployed bytecode, read from the upgradeable loader's ProgramData account. The ELF +/// starts 45 bytes in, past the loader's own header. +async fn deployed_program(programdata: Pubkey, name: &str) -> Vec { + let cache = std::env::temp_dir().join(format!("surfpool-phoenix-{name}.so")); + match std::fs::read(&cache) { + Ok(bytes) if bytes.len() > 200_000 => bytes, + _ => { + let bytes = fetch(&[programdata]).await.remove(0).data[45..].to_vec(); + let _ = std::fs::write(&cache, &bytes); + bytes + } + } +} + +/// Refetching the graph per test is what exhausts a public endpoint: the perp asset map alone +/// is 1.6MB, so every test reads the same fork state from one cached fetch. +fn live_graph_cache() -> &'static tokio::sync::Mutex> { + static CACHE: std::sync::OnceLock>> = + std::sync::OnceLock::new(); + CACHE.get_or_init(|| tokio::sync::Mutex::new(None)) +} + +#[tokio::test(flavor = "multi_thread")] +async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { + // Collateral stress produces the risk condition: a trader with an open position and + // almost no collateral is liquidatable whichever way the position points. + let (collateral_locker, graph) = phoenix_behavior_locker().await; + let before = hawkeye_margin(&collateral_locker, &graph); + assert!( + before.position_count > 0, + "the discovered trader must hold a position for margin to mean anything" + ); + // The program itself says how much collateral this trader's positions require, so the + // stress target is derived from live state rather than picked. + assert!( + before.maintenance_margin_quote_lots > 0, + "no eligible live candidate: the discovered trader's positions require no margin" + ); + assert_eq!(before.is_liquidatable, 0, "the fork starts healthy"); + + collateral_locker + .register_scenario( + phoenix_collateral_scenario(graph.trader, serde_json::json!("1"), false), + Some(100), + ) + .unwrap(); + collateral_locker + .materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + let after_collateral = hawkeye_margin(&collateral_locker, &graph); + assert_eq!( + after_collateral.collateral_quote_lots, 1, + "the program reads the collateral the preparation wrote" + ); + assert!( + after_collateral.effective_collateral_quote_lots < before.effective_collateral_quote_lots, + "stressing collateral must lower what the risk engine can count on" + ); + + // The cascade prepares the same collateral at slot 0 and a mark shock at slot 1. What + // the deployed program reads is asserted; whether this particular position liquidates + // depends on its side, which the discovery does not choose. + let (mark_locker, graph) = phoenix_behavior_locker().await; + let (symbol, orderbook, spline) = graph.markets[0].clone(); + let (trader_account, global_account, perp_asset_map_account) = + mark_locker.with_svm_reader(|svm_reader| { + ( + svm_reader.get_account(&graph.trader).unwrap().unwrap(), + svm_reader + .get_account(&crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG) + .unwrap() + .unwrap(), + svm_reader + .get_account(&graph.perp_asset_map) + .unwrap() + .unwrap(), + ) + }); + let prepared_collateral = hawkeye_margin(&mark_locker, &graph).collateral_quote_lots / 2; + // The cascade is the two templates across slots: the collateral tool's scenario at slot 0 + // and the mark shock at slot 1, which is what a user composes in the editor. + let mut cascade = crate::scenarios::protocols::phoenix_eternal::v1::state_builder::build_phoenix_collateral_scenario( + graph.trader, + &trader_account, + &prepared_collateral.to_string(), + ) + .unwrap() + .scenario; + let mut shock = phoenix_direct_mark_scenario(graph.perp_asset_map, &symbol, "1", false) + .overrides + .remove(0); + shock.scenario_relative_slot = 1; + cascade.add_override(shock); + let _ = (&global_account, &perp_asset_map_account); + mark_locker.register_scenario(cascade, Some(100)).unwrap(); + mark_locker + .materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + let before_mark = hawkeye_bbo_for_market(&graph, &mark_locker, orderbook, spline); + assert_eq!( + hawkeye_margin(&mark_locker, &graph).collateral_quote_lots, + prepared_collateral, + "stage 0 prepares the collateral the cascade was built with" + ); + assert_ne!(before_mark.mark_price_ticks, 1); + mark_locker + .materialize_overrides_for_slot(&None, 101) + .await + .unwrap(); + let after_mark = hawkeye_bbo_for_market(&graph, &mark_locker, orderbook, spline); + assert_eq!( + after_mark.mark_price_ticks, 1, + "stage 1 shocks the mark the program itself reads" + ); + + // Reference divergence moves the cached index away from the mark and leaves the mark. + let (reference_locker, graph) = phoenix_behavior_locker().await; + let (symbol, orderbook, spline) = graph.markets[0].clone(); + let before_reference = hawkeye_bbo_for_market(&graph, &reference_locker, orderbook, spline); + reference_locker + .register_scenario( + phoenix_reference_price_scenario( + graph.perp_asset_map, + &symbol, + "80000", + "120000", + false, + ), + Some(100), + ) + .unwrap(); + reference_locker + .materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + let after_reference = hawkeye_bbo_for_market(&graph, &reference_locker, orderbook, spline); + assert_eq!( + after_reference.mark_price_ticks, before_reference.mark_price_ticks, + "the divergence preserves the mark it diverges from" + ); + assert_ne!( + after_reference.index_price_ticks, before_reference.index_price_ticks, + "the cached reference the program reads must move" + ); + assert_ne!( + after_reference.index_price_ticks, after_reference.mark_price_ticks, + "spot and perp references diverging from the mark is the whole scenario" + ); + + // The second live market proves the preparations are not market-specific. + let (second_locker, graph) = phoenix_behavior_locker().await; + let (symbol, orderbook, spline) = graph.markets[1].clone(); + let before_second = hawkeye_bbo_for_market(&graph, &second_locker, orderbook, spline); + second_locker + .register_scenario( + phoenix_direct_mark_scenario(graph.perp_asset_map, &symbol, "1", false), + Some(100), + ) + .unwrap(); + second_locker + .materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + let after_second = hawkeye_bbo_for_market(&graph, &second_locker, orderbook, spline); + assert_ne!(before_second.mark_price_ticks, 1); + assert_eq!(after_second.mark_price_ticks, 1); +} + +/// A surfnet holding the live Phoenix account graph and a discovered live trader, with the +/// two SBF programs loaded. Nothing here looks for a market or trader in an interesting +/// state: the collateral stress below produces the risk condition the assertions check. +async fn phoenix_behavior_locker() -> (SurfnetSvmLocker, PhoenixLiveGraph) { + let eternal_program = deployed_program(ETERNAL_PROGRAMDATA, "eternal").await; + let hawkeye_program = deployed_program(HAWKEYE_PROGRAMDATA, "hawkeye").await; + let graph = phoenix_live_graph().await; + + let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + let locker = SurfnetSvmLocker::new(svm); + locker.with_svm_writer(|svm_writer| { + let mut clock = svm_writer.inner.get_sysvar::(); + clock.slot = 0; + svm_writer.inner.set_sysvar(&clock); + svm_writer + .inner + .svm + .add_program(crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, &eternal_program) + .unwrap(); + svm_writer + .inner + .svm + .add_program(HAWKEYE_PROGRAM_ID, &hawkeye_program) + .unwrap(); + for (address, account) in &graph.accounts { + svm_writer.set_account(address, account.clone()).unwrap(); + } + }); + + (locker, graph) +} + +async fn phoenix_live_graph() -> PhoenixLiveGraph { + let mut cache = live_graph_cache().lock().await; + if let Some(cached) = cache.as_ref() { + return cached.clone(); + } + + let global_account = fetch(&[ + crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG, + ]) + .await + .remove(0); + let global = phoenix_rise_accounts::global_config::GlobalConfig::try_from_account_bytes( + &global_account.data, + ) + .expect("live GlobalConfig decodes"); + let perp_asset_map = Pubkey::new_from_array(global.perp_asset_map_key()); + let global_trader_index = Pubkey::new_from_array(global.global_trader_index_header_key()); + let active_trader_buffer = Pubkey::new_from_array(global.active_trader_buffer_header_key()); + + let supporting = fetch(&[perp_asset_map, global_trader_index, active_trader_buffer]).await; + let map = PerpAssetMap::try_from_account_bytes(&supporting[0].data) + .expect("live PerpAssetMap decodes"); + let symbols = + crate::scenarios::protocols::phoenix_eternal::v1::state_builder::phoenix_market_symbols( + perp_asset_map, + &supporting[0], + ) + .expect("live PerpAssetMap lists its markets"); + assert!( + symbols.len() >= 2, + "no eligible live candidate: the preparations claim to work for any market, which \ + needs two live ones to show, found {symbols:?}" + ); + + let mut markets = Vec::new(); + let mut accounts = vec![ + ( + crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG, + global_account, + ), + (perp_asset_map, supporting[0].clone()), + (global_trader_index, supporting[1].clone()), + (active_trader_buffer, supporting[2].clone()), + ]; + for symbol in symbols.into_iter().take(2) { + let entry = map + .find_by_symbol(&symbol) + .expect("symbol lookup") + .expect("listed symbol"); + let orderbook = + Pubkey::new_from_array(entry.metadata.static_market_params().market_account); + let spline = phoenix_rise_accounts::pda::derive_spline_collection_address( + &crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + &orderbook, + ); + let market_accounts = fetch(&[orderbook, spline]).await; + accounts.push((orderbook, market_accounts[0].clone())); + accounts.push((spline, market_accounts[1].clone())); + markets.push((symbol, orderbook, spline)); + } + + let (trader, trader_account) = crate::tests::phoenix::live_trader_with_position().await; + accounts.push((trader, trader_account)); + + let graph = PhoenixLiveGraph { + accounts, + global_trader_index, + active_trader_buffer, + perp_asset_map, + trader, + markets, + }; + *cache = Some(graph.clone()); + + graph +} + +/// The live accounts a Phoenix behavioral run needs, with the addresses the Hawkeye +/// margin view expects to be passed alongside them. +#[derive(Clone)] +struct PhoenixLiveGraph { + accounts: Vec<(Pubkey, Account)>, + global_trader_index: Pubkey, + active_trader_buffer: Pubkey, + perp_asset_map: Pubkey, + trader: Pubkey, + /// Two live markets, as symbol plus the orderbook and spline accounts the + /// Hawkeye reader wants for it. + markets: Vec<(String, Pubkey, Pubkey)>, +} + +fn hawkeye_margin(locker: &SurfnetSvmLocker, graph: &PhoenixLiveGraph) -> HawkeyeMarginView { + let payer = Keypair::new(); + locker.with_svm_writer(|svm_writer| { + svm_writer + .inner + .airdrop(&payer.pubkey(), 1_000_000_000) + .unwrap(); + let instruction = Instruction { + program_id: HAWKEYE_PROGRAM_ID, + accounts: vec![ + AccountMeta::new_readonly( + crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + false, + ), + AccountMeta::new_readonly( + crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG, + false, + ), + AccountMeta::new_readonly(graph.global_trader_index, false), + AccountMeta::new_readonly(graph.active_trader_buffer, false), + AccountMeta::new_readonly(graph.perp_asset_map, false), + AccountMeta::new_readonly(graph.trader, false), + ], + data: HAWKEYE_VIEW_MARGIN_DISCRIMINANT.to_vec(), + }; + let transaction = Transaction::new_signed_with_payer( + // A live trader carries more positions than the fixture did, and the margin view + // walks all of them. + &[ + ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), + instruction, + ], + Some(&payer.pubkey()), + &[&payer], + svm_writer.inner.svm.latest_blockhash(), + ); + let metadata = svm_writer.inner.send_transaction(transaction).unwrap(); + let margin = + bytemuck::pod_read_unaligned::(&metadata.return_data.data); + assert_eq!(margin.magic, HAWKEYE_MARGIN_RETURN_MAGIC); + margin + }) +} + +fn hawkeye_bbo_for_market( + graph: &PhoenixLiveGraph, + locker: &SurfnetSvmLocker, + orderbook: Pubkey, + spline: Pubkey, +) -> HawkeyeBboView { + let payer = Keypair::new(); + locker.with_svm_writer(|svm_writer| { + svm_writer + .inner + .airdrop(&payer.pubkey(), 1_000_000_000) + .unwrap(); + let instruction = Instruction { + program_id: HAWKEYE_PROGRAM_ID, + accounts: vec![ + AccountMeta::new_readonly( + crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + false, + ), + AccountMeta::new_readonly( + crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG, + false, + ), + AccountMeta::new_readonly(graph.global_trader_index, false), + AccountMeta::new_readonly(graph.active_trader_buffer, false), + AccountMeta::new_readonly(graph.perp_asset_map, false), + AccountMeta::new_readonly(orderbook, false), + AccountMeta::new_readonly(spline, false), + ], + data: HAWKEYE_VIEW_BBO_DISCRIMINANT.to_vec(), + }; + let transaction = Transaction::new_signed_with_payer( + // A live trader carries more positions than the fixture did, and the margin view + // walks all of them. + &[ + ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), + instruction, + ], + Some(&payer.pubkey()), + &[&payer], + svm_writer.inner.svm.latest_blockhash(), + ); + let metadata = svm_writer.inner.send_transaction(transaction).unwrap(); + let bbo = bytemuck::pod_read_unaligned::(&metadata.return_data.data); + assert_eq!(bbo.magic, HAWKEYE_BBO_RETURN_MAGIC); + bbo + }) +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +struct HawkeyeMarginView { + magic: u64, + version: u16, + position_count: u16, + risk_state: u8, + risk_tier: u8, + is_liquidatable: u8, + padding: u8, + collateral_quote_lots: i64, + effective_collateral_quote_lots: i64, + free_collateral_quote_lots: i64, + withdrawable_collateral_quote_lots: u64, + initial_margin_quote_lots: u64, + maintenance_margin_quote_lots: u64, + cancel_margin_quote_lots: u64, + backstop_margin_quote_lots: u64, + high_risk_margin_quote_lots: u64, + unrealized_pnl_quote_lots: i64, + discounted_unrealized_pnl_quote_lots: i64, + unsettled_funding_quote_lots: i64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +struct HawkeyeBboView { + magic: u64, + version: u16, + flags: u8, + padding: [u8; 5], + best_bid_ticks: u64, + best_ask_ticks: u64, + mark_price_ticks: u64, + index_price_ticks: u64, + mark_price_last_updated_slot: u64, + index_price_last_updated_slot: u64, +} + +const HAWKEYE_PROGRAM_ID: Pubkey = + Pubkey::from_str_const("RiSeVw3ZjNfsaXPRb4mgaqYaEEt41pNNJoDvVh7pgQj"); + +const HAWKEYE_MARGIN_RETURN_MAGIC: u64 = 0x955f5b9d3dff253f; + +const HAWKEYE_BBO_RETURN_MAGIC: u64 = 0xefca1fa31fa74171; + +fn phoenix_collateral_scenario( + trader: Pubkey, + collateral: serde_json::Value, + fetch_before_use: bool, +) -> surfpool_types::Scenario { + let mut scenario = surfpool_types::Scenario::new( + "Phoenix collateral stress".to_string(), + "Phoenix Trader collateral override".to_string(), + ); + let mut instance = surfpool_types::OverrideInstance::new( + "phoenix-trader-collateral-stress".to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(trader.to_string()), + ) + .with_values(HashMap::from([( + "quote_lot_collateral".to_string(), + collateral, + )])); + instance.fetch_before_use = fetch_before_use; + scenario.add_override(instance); + scenario +} + +fn phoenix_direct_mark_scenario( + perp_asset_map: Pubkey, + symbol: &str, + target_ticks: &str, + fetch_before_use: bool, +) -> surfpool_types::Scenario { + let mut scenario = surfpool_types::Scenario::new( + "Phoenix direct mark risk shock".to_string(), + "Phoenix direct mark override".to_string(), + ); + let mut instance = surfpool_types::OverrideInstance::new( + "phoenix-direct-mark-risk-shock".to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()), + ) + .with_values(HashMap::from([ + ("symbol".to_string(), serde_json::json!(symbol)), + ("target_ticks".to_string(), serde_json::json!(target_ticks)), + ])); + instance.fetch_before_use = fetch_before_use; + scenario.add_override(instance); + scenario +} + +fn phoenix_reference_price_scenario( + perp_asset_map: Pubkey, + symbol: &str, + spot_ticks: &str, + perp_ticks: &str, + fetch_before_use: bool, +) -> surfpool_types::Scenario { + let mut scenario = surfpool_types::Scenario::new( + "Phoenix spot/perp reference divergence".to_string(), + "Phoenix reference-price override".to_string(), + ); + let mut instance = surfpool_types::OverrideInstance::new( + "phoenix-reference-price-divergence".to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()), + ) + .with_values(HashMap::from([ + ("symbol".to_string(), serde_json::json!(symbol)), + ("spot_ticks".to_string(), serde_json::json!(spot_ticks)), + ("perp_ticks".to_string(), serde_json::json!(perp_ticks)), + ])); + instance.fetch_before_use = fetch_before_use; + scenario.add_override(instance); + scenario +} + +#[tokio::test(flavor = "multi_thread")] +async fn materialize_patches_only_phoenix_trader_collateral() { + let trader = Pubkey::new_unique(); + let base = phoenix_trader_fixture(6_996_825_500); + let scenario = phoenix_collateral_scenario(trader, serde_json::json!("371499999"), false); + let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + let locker = SurfnetSvmLocker::new(svm); + locker.with_svm_writer(|svm_writer| { + svm_writer + .set_account( + &trader, + Account { + lamports: 1, + data: base.clone(), + owner: crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + ) + .unwrap(); + }); + + locker.register_scenario(scenario, Some(100)).unwrap(); + locker + .materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + + let after = locker + .with_svm_reader(|svm_reader| svm_reader.get_account(&trader)) + .unwrap() + .unwrap(); + let header = TraderHeader::try_read_from_account_bytes(&after.data).unwrap(); + assert_eq!( + header.trader_state.quote_lot_collateral.as_inner(), + 371_499_999 + ); + assert_eq!(after.data.len(), base.len()); + assert!( + base.iter() + .zip(&after.data) + .enumerate() + .filter(|(_, (before, after))| before != after) + .all(|(offset, _)| (88..96).contains(&offset)) + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn materialize_applies_phoenix_collateral_without_fetch_before_use() { + let trader = Pubkey::new_unique(); + let scenario = phoenix_collateral_scenario(trader, serde_json::json!("371499999"), false); + let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + let locker = SurfnetSvmLocker::new(svm); + locker.with_svm_writer(|svm_writer| { + svm_writer + .set_account( + &trader, + Account { + lamports: 1, + data: phoenix_trader_fixture(6_996_825_500), + owner: crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + ) + .unwrap(); + }); + + locker.register_scenario(scenario, Some(100)).unwrap(); + locker + .materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + + let after = locker + .with_svm_reader(|svm_reader| svm_reader.get_account(&trader)) + .unwrap() + .unwrap(); + let header = TraderHeader::try_read_from_account_bytes(&after.data).unwrap(); + assert_eq!( + header.trader_state.quote_lot_collateral.as_inner(), + 371_499_999 + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn materialize_applies_a_phoenix_direct_mark_override() { + let perp_asset_map = Pubkey::new_unique(); + let base = crate::scenarios::protocols::phoenix_eternal::v1::state_builder::tests::perp_asset_map_fixture(); + let mut scenario = surfpool_types::Scenario::new( + "Phoenix direct mark risk shock".to_string(), + "Phoenix direct mark override".to_string(), + ); + let mut instance = surfpool_types::OverrideInstance::new( + "phoenix-direct-mark-risk-shock".to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()), + ) + .with_values(HashMap::from([ + ("symbol".to_string(), serde_json::json!("SOL")), + ("target_ticks".to_string(), serde_json::json!("1")), + ])); + instance.fetch_before_use = false; + scenario.add_override(instance); + let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + let locker = SurfnetSvmLocker::new(svm); + locker.with_svm_writer(|svm_writer| { + svm_writer + .set_account( + &perp_asset_map, + Account { + lamports: 1, + data: base.clone(), + owner: crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + ) + .unwrap(); + }); + + locker.register_scenario(scenario, Some(100)).unwrap(); + locker + .materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + + let after = locker + .with_svm_reader(|svm_reader| svm_reader.get_account(&perp_asset_map)) + .unwrap() + .unwrap(); + let map = PerpAssetMap::try_from_account_bytes(&after.data).unwrap(); + let mark_price = map + .find_by_symbol("SOL") + .unwrap() + .unwrap() + .metadata + .oracle_price() + .mark_price + .price; + assert_eq!(mark_price.ticks.as_inner(), 1); + assert_eq!(mark_price.slot, 100); + assert_eq!(after.data.len(), base.len()); + assert!( + base.iter() + .zip(&after.data) + .filter(|(before, after)| before != after) + .count() + <= 16 + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn materialize_refreshes_phoenix_reference_price_slots() { + let perp_asset_map = Pubkey::new_unique(); + let base = crate::scenarios::protocols::phoenix_eternal::v1::state_builder::tests::perp_asset_map_fixture(); + let before = PerpAssetMap::try_from_account_bytes(&base).unwrap(); + let before_mark = before + .find_by_symbol("SOL") + .unwrap() + .unwrap() + .metadata + .oracle_price() + .mark_price + .price + .ticks + .as_inner(); + let scenario = phoenix_reference_price_scenario(perp_asset_map, "SOL", "8000", "7000", false); + let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + let locker = SurfnetSvmLocker::new(svm); + locker.with_svm_writer(|svm_writer| { + svm_writer + .set_account( + &perp_asset_map, + Account { + lamports: 1, + data: base, + owner: crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + ) + .unwrap(); + }); + + let mut scenario = scenario; + scenario.overrides[0].account = + surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()); + scenario.overrides[0] + .values + .insert("symbol".to_string(), serde_json::json!("SOL")); + locker.register_scenario(scenario, Some(100)).unwrap(); + locker + .materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + + let after = locker + .with_svm_reader(|svm_reader| svm_reader.get_account(&perp_asset_map)) + .unwrap() + .unwrap(); + let map = PerpAssetMap::try_from_account_bytes(&after.data).unwrap(); + let entry = map.find_by_symbol("SOL").unwrap().unwrap(); + let price = entry.metadata.oracle_price(); + + assert_eq!(price.mark_price.price.ticks.as_inner(), before_mark); + assert!( + price + .mark_price + .spot_price_component + .last_exchange_spot_price + .iter() + .all(|value| value.slot == 100 && value.ticks.as_inner() == 8_000) + ); + assert!( + price + .mark_price + .perp_price_component + .last_exchange_perp_price + .iter() + .all(|value| value.slot == 100 && value.ticks.as_inner() == 7_000) + ); +} + +fn phoenix_trader_fixture(collateral: i64) -> Vec { + let header_len = core::mem::size_of::(); + let mut data = vec![0_u8; header_len + 16 + 80]; + data[..8].copy_from_slice(&PhoenixAccount::Trader.discriminant()); + data[88..96].copy_from_slice(&collateral.to_le_bytes()); + data[112..116].copy_from_slice(&2_u32.to_le_bytes()); + data[header_len..header_len + 8].copy_from_slice(&1_u64.to_le_bytes()); + data[header_len + 8..header_len + 16].copy_from_slice(&2_u64.to_le_bytes()); + data[header_len + 16..header_len + 24].copy_from_slice(&42_u64.to_le_bytes()); + data +} diff --git a/crates/core/src/tests/pump/mod.rs b/crates/core/src/tests/pump/mod.rs index 960ce8e2f..ba8917c73 100644 --- a/crates/core/src/tests/pump/mod.rs +++ b/crates/core/src/tests/pump/mod.rs @@ -45,18 +45,14 @@ use crate::{ }, }, storage::tests::TestType, - surfnet::{ - GetAccountResult, locker::SurfnetSvmLocker, remote::SurfnetRemoteClient, svm::SurfnetSvm, - }, + surfnet::{locker::SurfnetSvmLocker, svm::SurfnetSvm}, tests::{ helpers::get_free_port, integration::{RunloopGuard, spawn_runloop, wait_for_ready_and_connected}, + live::{DEFAULT_RPC_URL, RPC_URL_ENV, diff_indices, fetch}, }, }; -const RPC_URL_ENV: &str = "SURFPOOL_TEST_RPC_URL"; -const DEFAULT_RPC_URL: &str = "https://api.mainnet-beta.solana.com"; - const PUMP: Pubkey = Pubkey::from_str_const("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); const PAMM: Pubkey = Pubkey::from_str_const("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"); const FEE_PROGRAM: Pubkey = Pubkey::from_str_const("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"); @@ -103,38 +99,6 @@ const POOL_LP_SUPPLY_OFFSET: usize = 203; const POOL_VIRTUAL_QUOTE_RESERVES_OFFSET: usize = 245; const POOL_COIN_CREATOR_OFFSET: usize = 211; -/// Fetches the accounts in one request, so every account returned is from the same slot. -async fn fetch(addresses: &[Pubkey]) -> Vec { - let client = SurfnetRemoteClient::new( - std::env::var(RPC_URL_ENV).unwrap_or_else(|_| DEFAULT_RPC_URL.to_string()), - ); - - client - .get_multiple_accounts(addresses, CommitmentConfig::confirmed()) - .await - .unwrap_or_else(|e| panic!("failed to fetch {addresses:?} from mainnet: {e}")) - .into_iter() - .zip(addresses) - .map(|(result, address)| match result { - GetAccountResult::FoundAccount(_, account, _) - | GetAccountResult::FoundCoupledAccount((_, account), _, _) => account, - GetAccountResult::None(_) => { - panic!("{address} no longer exists on mainnet; the test needs a new address") - } - }) - .collect() -} - -/// Byte indices at which two buffers differ. -fn diff_indices(a: &[u8], b: &[u8]) -> Vec { - a.iter() - .zip(b.iter()) - .enumerate() - .filter(|(_, (x, y))| x != y) - .map(|(i, _)| i) - .collect() -} - /// A failure here means a bundled IDL disagrees with the live on-chain layout. #[tokio::test] async fn real_mainnet_accounts_round_trip_unchanged() { diff --git a/crates/mcp/src/surfpool/mod.rs b/crates/mcp/src/surfpool/mod.rs index d98665cbd..4cad1f69b 100644 --- a/crates/mcp/src/surfpool/mod.rs +++ b/crates/mcp/src/surfpool/mod.rs @@ -19,6 +19,7 @@ use start_surfnet::StartSurfnetResponse; use surfpool_core::{ scenarios::{ TemplateRegistry, + protocols::phoenix_eternal::v1::state_builder::build_phoenix_collateral_scenario, protocols::pump::v1::graduation_builder::{ build_pump_graduation_scenario, pump_graduation_addresses, }, @@ -151,6 +152,21 @@ pub struct GetTemplateParams { pub template_id: String, } +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct CreatePhoenixCollateralScenarioParams { + #[schemars(description = "Phoenix Eternal Trader account pubkey.")] + pub trader: String, + #[schemars( + description = "Exact signed collateral target in quote lots, encoded as a decimal string." + )] + pub target_quote_lots: String, + #[schemars( + description = "The port of the target running local surfnet instance (e.g., 8899, 18899, 28899, etc.). Omit to use the default port, 8899." + )] + pub surfnet_port: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct StartSurfnetWithTokenAccountsParams { #[schemars( @@ -1023,6 +1039,45 @@ impl Surfpool { self.stage_scenario(preparation.scenario).await } + #[tool( + description = "Creates an editable Phoenix Eternal Trader collateral-stress scenario. Requires a Trader pubkey and exact signed quote lots as a decimal string. The backend fetches and validates the live Trader account. This prepares risk state; it does not guarantee or execute liquidation." + )] + async fn create_phoenix_collateral_scenario( + &self, + Parameters(params): Parameters, + ) -> Result { + let trader = match Pubkey::from_str(params.trader.trim()) { + Ok(trader) => trader, + Err(error) => { + return Ok(scenario_tool_error(format!( + "Invalid Trader pubkey: {error}" + ))); + } + }; + let accounts = match self + .fetch_surfnet_accounts(params.surfnet_port, &[trader]) + .await + { + Ok(accounts) => accounts, + Err(error) => return Ok(scenario_tool_error(error)), + }; + let Some(trader_account) = accounts[0].as_ref() else { + return Ok(scenario_tool_error(format!( + "Phoenix Trader account {trader} was not found" + ))); + }; + let preparation = match build_phoenix_collateral_scenario( + trader, + trader_account, + ¶ms.target_quote_lots, + ) { + Ok(preparation) => preparation, + Err(error) => return Ok(scenario_tool_error(error.to_string())), + }; + + self.stage_scenario(preparation.scenario).await + } + #[tool( description = "Lists all override templates as a light index: {id, name, description, protocol, accountType, tags, hasLlmContext}. Call this first to pick a templateId, then get_override_template for that one template's full detail (properties, address, llmContext). Constants are resolved with search_constant_options." )] @@ -1349,6 +1404,43 @@ mod tests { ); } + #[tokio::test] + async fn phoenix_collateral_rejects_invalid_inputs_before_rpc() { + let surfpool = Surfpool::new(); + + let collateral = surfpool + .create_phoenix_collateral_scenario(Parameters(CreatePhoenixCollateralScenarioParams { + surfnet_port: None, + trader: "not-a-pubkey".to_string(), + target_quote_lots: "1".to_string(), + })) + .await + .expect("tool result"); + assert!( + json_of(&collateral)["error"] + .as_str() + .expect("error") + .contains("Invalid Trader pubkey") + ); + } + + #[test] + fn phoenix_collateral_requires_exact_string_inputs() { + assert!( + serde_json::from_value::(serde_json::json!({ + "trader": "GHkq1eHeZGi96RmGPZ23e3BDEcdfpPzsPwpYdm17SWgc", + })) + .is_err() + ); + assert!( + serde_json::from_value::(serde_json::json!({ + "trader": "GHkq1eHeZGi96RmGPZ23e3BDEcdfpPzsPwpYdm17SWgc", + "targetQuoteLots": 1, + })) + .is_err() + ); + } + fn json_of(result: &CallToolResult) -> serde_json::Value { let text = &result.content[0].as_text().expect("text content").text; serde_json::from_str(text).expect("valid JSON payload") From 4a1abdafbb582672f82197688f9afc1a71e84425 Mon Sep 17 00:00:00 2001 From: 92Infinitus92 <92georgepetroff92@gmail.com> Date: Tue, 8 Sep 2026 08:45:03 +0300 Subject: [PATCH 2/6] refactor(phoenix): IDL-driven overrides, live catalog, hot-trader collateral Move collateral, direct-mark, and reference scenarios onto the generic IDL override path and drop the bespoke forge for the parts the IDL can name. Fetch the market catalog live via list_phoenix_markets instead of hardcoding it. Write a hot trader's effective collateral into GlobalTraderIndex, where the risk engine actually reads it, not the stale copy in the Trader account. Remove the dead cascade example and editor schema and trim the builder. --- .../phoenix-eternal-liquidation-cascade.json | 41 - .../protocols/phoenix-eternal/README.md | 45 +- .../phoenix-eternal/v1/collateral.rs | 471 +++++++++ .../phoenix-eternal/v1/editor-schema.json | 74 -- .../protocols/phoenix-eternal/v1/idl.json | 1 + .../protocols/phoenix-eternal/v1/mod.rs | 1 + .../phoenix-eternal/v1/overrides.yaml | 259 +---- .../phoenix-eternal/v1/state_builder.rs | 907 ++++-------------- crates/core/src/scenarios/registry.rs | 31 +- crates/core/src/surfnet/svm.rs | 149 ++- crates/core/src/tests/phoenix/mod.rs | 586 ++++++----- crates/mcp/src/surfpool/mod.rs | 97 +- crates/types/src/scenarios.rs | 26 +- 13 files changed, 1285 insertions(+), 1403 deletions(-) delete mode 100644 crates/core/src/scenarios/examples/phoenix-eternal-liquidation-cascade.json create mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs delete mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/v1/editor-schema.json create mode 100644 crates/core/src/scenarios/protocols/phoenix-eternal/v1/idl.json diff --git a/crates/core/src/scenarios/examples/phoenix-eternal-liquidation-cascade.json b/crates/core/src/scenarios/examples/phoenix-eternal-liquidation-cascade.json deleted file mode 100644 index a41a9085e..000000000 --- a/crates/core/src/scenarios/examples/phoenix-eternal-liquidation-cascade.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "phoenix-eternal-liquidation-cascade-example", - "name": "Phoenix Eternal Liquidation Cascade - BTC", - "description": "Stresses one Phoenix Eternal Trader's collateral, then shocks the BTC mark price a slot later, so the trader crosses into liquidation territory while the run is live. Replace the trader pubkey with an account that exists on your fork; collateral can only be lowered, since an override cannot mint the vault backing it. Both values are decimal strings because they span the full i64/u64 range.", - "tags": [ - "phoenix-eternal", - "liquidation", - "multi-slot" - ], - "overrides": [ - { - "id": "trader-collateral-stress", - "templateId": "phoenix-trader-collateral-stress", - "label": "Phoenix Trader collateral stress", - "scenarioRelativeSlot": 0, - "enabled": true, - "fetchBeforeUse": true, - "account": { - "pubkey": "" - }, - "values": { - "quote_lot_collateral": "900000000" - } - }, - { - "id": "btc-direct-mark-shock", - "templateId": "phoenix-direct-mark-risk-shock", - "label": "Phoenix BTC direct mark risk shock", - "scenarioRelativeSlot": 1, - "enabled": true, - "fetchBeforeUse": true, - "account": { - "pubkey": "2nHGAaEw3D5dd4hVueaUNoygkQFmoeKqRQWnSPqSMFUC" - }, - "values": { - "symbol": "BTC", - "target_ticks": "1" - } - } - ] -} diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md index 058616f00..5371fbc29 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md @@ -19,7 +19,7 @@ values as decimal strings so values outside JavaScript's safe integer range rema 1. Start an online Surfpool fork and open Studio. 2. Open **Scenario presets**, choose **Phoenix state**, then select the state goal. -3. For price scenarios, type the market symbol, such as `BTC`. For collateral stress, enter a +3. For price scenarios, select a market from the live dropdown. For collateral stress, enter a Phoenix Eternal Trader account. 4. Enter the target values and create the scenario. 5. Inspect the generated override, then press **Play** to activate it. @@ -34,7 +34,7 @@ remains empty until a client sends a transaction against the prepared state. Phoenix has no HTTP routes of its own. The perp asset map address is a GlobalConfig field rather than a PDA, but both market templates carry that address directly, so those two scenarios need no tool. Only the collateral scenario needs one, because it must read and validate the live Trader -account first — the one tool listed below. Studio's preset calls that tool for collateral and posts +account first. Studio's preset calls that tool for collateral and posts the market templates directly. Accounts are read through the Surfnet's own RPC, so local state wins and only missing accounts fall @@ -43,24 +43,31 @@ accounts you changed locally. ## Use through MCP -One tool, for the one scenario a template cannot express: +The collateral builder and live market catalog are available through MCP: | Tool | Required parameters | | --- | --- | | `create_phoenix_collateral_scenario` | `trader`, `targetQuoteLots` | +| `list_phoenix_markets` | None; optional `surfnetPort` | -It returns a Studio editor URL. The backend reads the live Trader account and refuses a target -the global vault does not back, which an LLM cannot bypass. +The collateral tool returns a Studio editor URL. The backend reads the live Trader account and refuses a target +above the trader’s effective collateral. The market scenarios need no tool: their templates carry the perp asset map address, so a client fills in the values and posts the scenario to `/v1/scenarios`, which is what the Studio preset -does. The liquidation cascade is those two templates in one scenario at slots 0 and 1 — -[`phoenix-eternal-liquidation-cascade.json`](../../examples/phoenix-eternal-liquidation-cascade.json) -is a ready example to post or import. +does. The liquidation cascade combines those two templates in one scenario at slots 0 and 1. ## What each preparation guarantees -- Collateral stress changes only `quote_lot_collateral` in the selected Trader account. +- Collateral stress uses the IDL for `Trader.traderState.quoteLotCollateral`. For a hot trader, + Phoenix reads its effective collateral from `GlobalTraderIndex`, so Play also locates the + reachable entry by the trader key and encodes its `TraderState.quoteLotCollateral` with the + same IDL. Both accounts retain their length and every unrelated byte. A cold trader needs + only its Trader account. The builder's increase guard uses effective index collateral for + hot traders, rather than the stale copy in their Trader account. +- Hot collateral preparations currently support a single-arena `GlobalTraderIndex`. Missing, + corrupt, or multi-arena indexes fail before collateral writes. Node offsets are resolved + again at Play; no trader address or node offset is pinned in the scenario. - Direct mark shock changes only the selected market's mark-price ticks and the mark-price slot, which is stamped with the slot the override materializes at so the program reads the new mark as fresh. @@ -93,6 +100,13 @@ or liquidates depends on the transaction, the selected account, and the rest of | Scenario is green but the Transaction Inspector is empty | The state is active, but no client transaction has been sent yet. | | A transaction does not produce the expected economic result | Confirm its accounts and instruction path consume the field changed by the selected preparation. | +Check the same market before applying an override. A cached `ActiveTraderBuffer` can lack positions +referenced by a more recently fetched orderbook, causing BBO to fail even before price validation. +Price-slot refreshes cannot repair those missing references. Behavioral tests fetch the account graph +and Clock together with `getMultipleAccounts` after discovering the addresses; they do not rewrite +oracle timestamps or fabricate trader positions. An old fork can also fail price-staleness guards +independently of whether the override wrote the correct values. + ## Layout drift Phoenix is zero-copy, so decoding an account built by these tests can never disagree with the @@ -108,6 +122,12 @@ and prove an override on a live account changes only its target bytes. Set ## Behavioral verification +To check reference prices independently on the live SOL and BTC markets: + +```sh +cargo test -p surfpool-core --features integration-tests reference_prices_change_hawkeye_index_on_live_markets +``` + The behavioral test runs with the rest of the integration suite; there is nothing to install by hand and no snapshots to keep current, since the deployed bytecode is fetched straight from the chain: @@ -116,13 +136,18 @@ chain: cargo test -p surfpool-core --features integration-tests tests::phoenix ``` -It forks the live account graph, discovers a live Trader that carries collateral and a long +It forks the live account graph, discovers a live hot Trader that carries collateral and a long position, and loads the deployed Phoenix Eternal and Hawkeye bytecode straight from their ProgramData accounts (`B5ayDaz9HegiNZqYeBtcFqfZBVSGwjB2CJgHshoSfMQg` and `Gv1WgG864CQqF5vedJVbpnhpRpRbTW1A7SyARzSw9B4Y`), cached under the system temp directory as `surfpool-phoenix-eternal.so` and `surfpool-phoenix-hawkeye.so`. Delete those files to pick up a program upgrade; the cached bytecode is otherwise reused as-is. +The collateral check uses the production builder, verifies creation leaves the Trader and +GlobalTraderIndex unchanged, and compares both complete accounts after Play: only their +collateral fields may change. A real Hawkeye transaction must read collateral `1` and report +the trader liquidatable. This runs in the isolated test VM, outside Studio's Transaction Inspector. + The raw material is selected, not arbitrary: it walks the program's Trader accounts for one that carries collateral, and for the mark-shock and cascade tests, one that also holds a long position, since a downward shock only threatens a long. The risk condition itself — a trader actually diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs new file mode 100644 index 000000000..0b4af74cb --- /dev/null +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs @@ -0,0 +1,471 @@ +use std::{collections::HashSet, ops::Range}; + +use phoenix_rise_accounts::{ + PhoenixAccount, PhoenixAccountDecodeError, multi_arena::MultiArenaHeader, trader::TraderHeader, +}; +use solana_account::Account; +use solana_pubkey::Pubkey; + +use super::state_builder::PHOENIX_ETERNAL_PROGRAM_ID; +use crate::error::{SurfpoolError, SurfpoolResult}; + +pub fn trader_header(trader: &Pubkey, account: &Account) -> SurfpoolResult { + if account.owner != PHOENIX_ETERNAL_PROGRAM_ID { + return Err(SurfpoolError::invalid_account_owner( + *trader, + None::, + )); + } + TraderHeader::try_read_from_account_bytes(&account.data).map_err(|error| { + SurfpoolError::invalid_account_data( + trader, + "Expected a valid Phoenix Eternal Trader account", + Some(error), + ) + }) +} + +pub fn index_trader_state_range( + index: &Account, + trader_key: &[u8; 32], +) -> SurfpoolResult> { + let invalid = || SurfpoolError::internal("Invalid Phoenix GlobalTraderIndex tree"); + if index.owner != PHOENIX_ETERNAL_PROGRAM_ID { + return Err(SurfpoolError::internal( + "Expected a Phoenix-owned GlobalTraderIndex account", + )); + } + let header = MultiArenaHeader::try_from_account_bytes( + "GlobalTraderIndex", + &index.data, + PhoenixAccount::GlobalTraderIndexHeader.discriminant(), + ) + .map_err(|error| { + SurfpoolError::internal(format!("Invalid Phoenix GlobalTraderIndex: {error}")) + })?; + if header.num_arenas() != 1 || header.superblock().num_active_arenas() != 1 { + return Err(SurfpoolError::internal( + "Phoenix collateral overrides currently require a single-arena GlobalTraderIndex", + )); + } + // MultiArenaHeader (48), superblock (32), tree root and padding (16), then + // 1-based Sokoban nodes: four u32 registers, a 32-byte key, and IDL TraderState. + const NODES_START: usize = 96; + const NODE_LEN: usize = 64; + let data = &index.data; + if data.len() < NODES_START || !(data.len() - NODES_START).is_multiple_of(NODE_LEN) { + return Err(invalid()); + } + let capacity = (data.len() - NODES_START) / NODE_LEN; + let read_u32 = |offset| u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()); + let mut pending = vec![read_u32(80)]; + let mut visited = HashSet::new(); + let mut found = None; + while let Some(node) = pending.pop() { + if node == 0 { + continue; + } + if node >= header.superblock().bump_index() + || node as usize > capacity + || !visited.insert(node) + { + return Err(invalid()); + } + let start = NODES_START + (node as usize - 1) * NODE_LEN; + pending.extend([read_u32(start), read_u32(start + 4)]); + if data[start + 16..start + 48] == trader_key[..] { + if found.is_some() { + return Err(invalid()); + } + found = Some(start + 48..start + 64); + } + } + if visited.len() != header.superblock().size() as usize { + return Err(invalid()); + } + found.ok_or_else(|| { + SurfpoolError::internal("Hot Phoenix Trader has no reachable GlobalTraderIndex entry") + }) +} + +pub fn effective_collateral(header: &TraderHeader, index: Option<&Account>) -> SurfpoolResult { + if !header.trader_state.is_hot() { + return Ok(header.trader_state.quote_lot_collateral.as_inner()); + } + let index = index.ok_or_else(|| { + SurfpoolError::internal("Hot Phoenix Trader requires its GlobalTraderIndex account") + })?; + let range = index_trader_state_range(index, &header.key)?; + Ok(i64::from_le_bytes( + index.data[range.start..range.start + 8].try_into().unwrap(), + )) +} + +pub fn collateral_override_value(value: &serde_json::Value) -> SurfpoolResult { + value + .as_i64() + .or_else(|| value.as_str().and_then(|value| value.parse::().ok())) + .map(serde_json::Value::from) + .ok_or_else(|| { + SurfpoolError::internal("Phoenix collateral must be a signed 64-bit integer") + }) +} + +#[cfg(test)] +mod tests { + use phoenix_rise_accounts::trader::TRADER_CAPABILITY_HOT; + + use super::*; + use crate::scenarios::protocols::phoenix_eternal::v1::state_builder::build_phoenix_collateral_scenario; + + const FIRST_KEY: [u8; 32] = [11; 32]; + const SECOND_KEY: [u8; 32] = [22; 32]; + + #[test] + fn collateral_values_preserve_signed_integer_precision() { + for value in [i64::MIN, -9_007_199_254_740_993, 0, i64::MAX] { + assert_eq!( + collateral_override_value(&serde_json::json!(value.to_string())).unwrap(), + serde_json::json!(value) + ); + assert_eq!( + collateral_override_value(&serde_json::json!(value)).unwrap(), + serde_json::json!(value) + ); + } + for value in [ + serde_json::json!("9223372036854775808"), + serde_json::json!(-1.5), + serde_json::json!(null), + ] { + assert!(collateral_override_value(&value).is_err()); + } + } + + fn write_u32(data: &mut [u8], offset: usize, value: u32) { + data[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + } + + fn index_account() -> Account { + let mut data = vec![0; 96 + 3 * 64]; + data[..8].copy_from_slice(&PhoenixAccount::GlobalTraderIndexHeader.discriminant()); + write_u32(&mut data, 48, 2); + data[52..54].copy_from_slice(&1_u16.to_le_bytes()); + data[54..56].copy_from_slice(&1_u16.to_le_bytes()); + write_u32(&mut data, 56, 3); + write_u32(&mut data, 60, 4); + write_u32(&mut data, 64, 3); + write_u32(&mut data, 80, 2); + write_u32(&mut data, 160, 1); + write_u32(&mut data, 104, 2); + for (slot, key, collateral) in [ + (0, FIRST_KEY, 111_i64), + (1, SECOND_KEY, 222_i64), + (2, FIRST_KEY, 999_i64), + ] { + let start = 96 + slot * 64; + data[start + 16..start + 48].copy_from_slice(&key); + data[start + 48..start + 56].copy_from_slice(&collateral.to_le_bytes()); + write_u32(&mut data, start + 56, TRADER_CAPABILITY_HOT); + } + Account { + data, + owner: PHOENIX_ETERNAL_PROGRAM_ID, + ..Account::default() + } + } + + fn trader_account(key: [u8; 32], collateral: i64, hot: bool) -> Account { + let mut data = vec![0; core::mem::size_of::() + 16]; + data[..8].copy_from_slice(&PhoenixAccount::Trader.discriminant()); + data[24..56].copy_from_slice(&key); + data[88..96].copy_from_slice(&collateral.to_le_bytes()); + if hot { + write_u32(&mut data, 96, TRADER_CAPABILITY_HOT); + } + Account { + data, + owner: PHOENIX_ETERNAL_PROGRAM_ID, + ..Account::default() + } + } + + #[test] + fn index_lookup_selects_reachable_keys_and_ignores_freed_duplicate() { + let index = index_account(); + assert_eq!( + index_trader_state_range(&index, &FIRST_KEY).unwrap(), + 144..160 + ); + assert_eq!( + index_trader_state_range(&index, &SECOND_KEY).unwrap(), + 208..224 + ); + + let mut only_freed_match = index.clone(); + only_freed_match.data[112..144].copy_from_slice(&[33; 32]); + assert!(index_trader_state_range(&only_freed_match, &FIRST_KEY).is_err()); + } + + #[test] + fn index_lookup_rejects_cycles_wrong_size_and_missing_key() { + let mut cycle = index_account(); + write_u32(&mut cycle.data, 96, 2); + assert!(index_trader_state_range(&cycle, &FIRST_KEY).is_err()); + + let mut wrong_size = index_account(); + write_u32(&mut wrong_size.data, 48, 3); + assert!(index_trader_state_range(&wrong_size, &FIRST_KEY).is_err()); + assert!(index_trader_state_range(&index_account(), &[44; 32]).is_err()); + } + + #[test] + fn index_lookup_rejects_invalid_node_addresses_and_duplicate_reachable_keys() { + let mut out_of_bounds = index_account(); + write_u32(&mut out_of_bounds.data, 60, 100); + write_u32(&mut out_of_bounds.data, 164, 4); + assert!(index_trader_state_range(&out_of_bounds, &FIRST_KEY).is_err()); + + let mut unallocated = index_account(); + write_u32(&mut unallocated.data, 60, 2); + assert!(index_trader_state_range(&unallocated, &FIRST_KEY).is_err()); + + let mut duplicate = index_account(); + duplicate.data[176..208].copy_from_slice(&FIRST_KEY); + assert!(index_trader_state_range(&duplicate, &FIRST_KEY).is_err()); + } + + #[test] + fn index_lookup_rejects_wrong_owner_discriminator_and_truncated_layouts() { + let mut wrong_owner = index_account(); + wrong_owner.owner = Pubkey::new_unique(); + assert!(index_trader_state_range(&wrong_owner, &FIRST_KEY).is_err()); + + let mut wrong_discriminator = index_account(); + wrong_discriminator.data[..8].fill(0); + assert!(index_trader_state_range(&wrong_discriminator, &FIRST_KEY).is_err()); + + for len in [0, 79, 95, 96 + 3 * 64 - 1] { + let mut truncated = index_account(); + truncated.data.truncate(len); + assert!(index_trader_state_range(&truncated, &FIRST_KEY).is_err()); + } + } + + #[test] + fn index_lookup_rejects_unsupported_arena_counts() { + for (arenas, active) in [(0_u16, 1_u16), (2, 1), (1, 0), (1, 2)] { + let mut index = index_account(); + index.data[52..54].copy_from_slice(&arenas.to_le_bytes()); + index.data[54..56].copy_from_slice(&active.to_le_bytes()); + assert!(index_trader_state_range(&index, &FIRST_KEY).is_err()); + } + } + + #[test] + fn hot_trader_uses_effective_index_collateral_and_requires_index() { + let trader = Pubkey::new_from_array(FIRST_KEY); + let account = trader_account(FIRST_KEY, 9_999, true); + let header = trader_header(&trader, &account).unwrap(); + assert_eq!( + effective_collateral(&header, Some(&index_account())).unwrap(), + 111 + ); + assert!(effective_collateral(&header, None).is_err()); + } + + #[test] + fn collateral_builder_bounds_hot_targets_by_effective_state() { + let trader = Pubkey::new_from_array(FIRST_KEY); + let index = index_account(); + let stale_high = trader_account(FIRST_KEY, 9_999, true); + assert!( + build_phoenix_collateral_scenario(trader, &stale_high, "112", Some(&index)).is_err() + ); + assert!(build_phoenix_collateral_scenario(trader, &stale_high, "1", None).is_err()); + + let stale_low = trader_account(FIRST_KEY, 1, true); + let prepared = + build_phoenix_collateral_scenario(trader, &stale_low, "100", Some(&index)).unwrap(); + assert_eq!( + prepared.overrides[0].values["traderState.quoteLotCollateral"], + "100" + ); + assert_eq!(index, index_account()); + assert_eq!(stale_low, trader_account(FIRST_KEY, 1, true)); + } + + #[test] + fn cold_trader_keeps_standalone_collateral_and_increase_guard() { + let trader = Pubkey::new_from_array(FIRST_KEY); + let account = trader_account(FIRST_KEY, 50, false); + let header = trader_header(&trader, &account).unwrap(); + assert_eq!(effective_collateral(&header, None).unwrap(), 50); + assert_eq!( + effective_collateral(&header, Some(&index_account())).unwrap(), + 50 + ); + assert!(build_phoenix_collateral_scenario(trader, &account, "51", None).is_err()); + assert!(build_phoenix_collateral_scenario(trader, &account, "50", None).is_ok()); + } + + fn global_account(index: &Pubkey) -> Account { + use super::super::state_builder::PHOENIX_GLOBAL_CONFIG; + + let mut data = vec![0; 2_560]; + data[..8].copy_from_slice(&PhoenixAccount::GlobalConfiguration.discriminant()); + data[8..40].copy_from_slice(PHOENIX_GLOBAL_CONFIG.as_ref()); + data[392..424].copy_from_slice(index.as_ref()); + Account { + lamports: 1, + data, + owner: PHOENIX_ETERNAL_PROGRAM_ID, + ..Account::default() + } + } + + #[tokio::test] + async fn materialization_patches_selected_hot_trader_and_index_record_only() { + use super::super::state_builder::PHOENIX_GLOBAL_CONFIG; + use crate::surfnet::svm::SurfnetSvm; + + for (key, other_key, collateral_offset, field, target) in [ + ( + FIRST_KEY, + SECOND_KEY, + 144, + "traderState.quoteLotCollateral", + 1_i64, + ), + ( + SECOND_KEY, + FIRST_KEY, + 208, + "traderState.quoteLotCollateral", + -9_007_199_254_740_993, + ), + (FIRST_KEY, SECOND_KEY, 144, "quote_lot_collateral", 1), + ( + SECOND_KEY, + FIRST_KEY, + 208, + "quote_lot_collateral", + -9_007_199_254_740_993, + ), + ] { + let trader = Pubkey::new_from_array(key); + let other_trader = Pubkey::new_from_array(other_key); + let index_key = Pubkey::new_unique(); + let mut before_trader = trader_account(key, 9_999, true); + before_trader.lamports = 1; + let mut before_other = trader_account(other_key, 8_888, true); + before_other.lamports = 1; + let mut before_index = index_account(); + before_index.lamports = 1; + let global = global_account(&index_key); + let mut scenario = build_phoenix_collateral_scenario( + trader, + &before_trader, + &target.to_string(), + Some(&before_index), + ) + .unwrap(); + scenario.overrides[0].values = std::collections::HashMap::from([( + field.to_string(), + serde_json::json!(target.to_string()), + )]); + let (mut svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + svm.set_account(&trader, before_trader.clone()).unwrap(); + svm.set_account(&other_trader, before_other.clone()) + .unwrap(); + svm.set_account(&index_key, before_index.clone()).unwrap(); + svm.set_account(&PHOENIX_GLOBAL_CONFIG, global.clone()) + .unwrap(); + svm.register_scenario(scenario, Some(100)).unwrap(); + + assert_eq!(svm.get_account(&trader).unwrap().unwrap(), before_trader); + assert_eq!(svm.get_account(&index_key).unwrap().unwrap(), before_index); + svm.materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + + let mut expected_trader = before_trader; + expected_trader.data[88..96].copy_from_slice(&target.to_le_bytes()); + let mut expected_index = before_index; + expected_index.data[collateral_offset..collateral_offset + 8] + .copy_from_slice(&target.to_le_bytes()); + assert_eq!(svm.get_account(&trader).unwrap().unwrap(), expected_trader); + assert_eq!( + svm.get_account(&index_key).unwrap().unwrap(), + expected_index + ); + assert_eq!( + svm.get_account(&other_trader).unwrap().unwrap(), + before_other + ); + assert_eq!( + svm.get_account(&PHOENIX_GLOBAL_CONFIG).unwrap().unwrap(), + global + ); + } + } + + #[tokio::test] + async fn materialization_rejects_invalid_or_missing_index_without_partial_trader_write() { + use super::super::state_builder::PHOENIX_GLOBAL_CONFIG; + use crate::surfnet::svm::SurfnetSvm; + + for failure in [ + "cycle", + "missing key", + "missing account", + "conflicting fields", + ] { + let trader = Pubkey::new_from_array(FIRST_KEY); + let index_key = Pubkey::new_unique(); + let mut before_trader = trader_account(FIRST_KEY, 9_999, true); + before_trader.lamports = 1; + let mut before_index = index_account(); + before_index.lamports = 1; + let mut scenario = + build_phoenix_collateral_scenario(trader, &before_trader, "1", Some(&before_index)) + .unwrap(); + match failure { + "conflicting fields" => { + scenario.overrides[0] + .values + .insert("quote_lot_collateral".to_string(), serde_json::json!("2")); + } + "cycle" => write_u32(&mut before_index.data, 96, 2), + "missing key" => before_index.data[112..144].copy_from_slice(&[33; 32]), + _ => {} + } + let (mut svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + svm.set_account(&trader, before_trader.clone()).unwrap(); + svm.set_account(&PHOENIX_GLOBAL_CONFIG, global_account(&index_key)) + .unwrap(); + if failure != "missing account" { + svm.set_account(&index_key, before_index.clone()).unwrap(); + } + svm.register_scenario(scenario, Some(100)).unwrap(); + + assert!( + svm.materialize_overrides_for_slot(&None, 100) + .await + .is_err(), + "{failure}" + ); + assert_eq!( + svm.get_account(&trader).unwrap().unwrap(), + before_trader, + "{failure}" + ); + let after_index = svm.get_account(&index_key).unwrap(); + if failure == "missing account" { + assert!(after_index.is_none()); + } else { + assert_eq!(after_index.unwrap(), before_index, "{failure}"); + } + } + } +} diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/editor-schema.json b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/editor-schema.json deleted file mode 100644 index 104546a01..000000000 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/editor-schema.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "address": "EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih", - "metadata": { - "name": "phoenix_eternal_write_mode_schema", - "version": "0.3.4", - "spec": "0.1.0", - "description": "Editor schema for specialized Phoenix scenario writers; not an account serialization contract" - }, - "instructions": [], - "accounts": [ - { - "name": "TraderCollateralOverride", - "discriminator": [41, 97, 73, 105, 110, 214, 112, 9] - }, - { - "name": "DirectMarkOverride", - "discriminator": [142, 54, 115, 223, 124, 239, 108, 38] - }, - { - "name": "ReferencePriceOverride", - "discriminator": [210, 123, 24, 82, 132, 17, 53, 94] - } - ], - "types": [ - { - "name": "TraderCollateralOverride", - "type": { - "kind": "struct", - "fields": [ - { - "name": "quote_lot_collateral", - "type": "string" - } - ] - } - }, - { - "name": "DirectMarkOverride", - "type": { - "kind": "struct", - "fields": [ - { - "name": "symbol", - "type": "string" - }, - { - "name": "target_ticks", - "type": "string" - } - ] - } - }, - { - "name": "ReferencePriceOverride", - "type": { - "kind": "struct", - "fields": [ - { - "name": "symbol", - "type": "string" - }, - { - "name": "spot_ticks", - "type": "string" - }, - { - "name": "perp_ticks", - "type": "string" - } - ] - } - } - ] -} diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/idl.json b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/idl.json new file mode 100644 index 000000000..36fc514a2 --- /dev/null +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/idl.json @@ -0,0 +1 @@ +{"version": "0.2.0", "address": "EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih", "metadata": {"name": "Eternal", "version": "0.2.0", "spec": "0.1.0", "description": "Phoenix Eternal: a perpetual futures exchange", "address": "EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih", "origin": "anchor-compatible", "codama": {"nodesVersion": "1.6.0", "nodesFromAnchorVersion": "1.4.1", "renderersJsVersion": "2.2.0"}}, "docs": ["Anchor-compatible IDL for the Phoenix Eternal program.", "This IDL is maintained from the native Rust program surface and includes explicit instruction, account, and event discriminators for transaction and account decoding.", "PDA metadata is included where the program defines a canonical derivation; accounts without PDA metadata are supplied as explicit accounts or are stored in GlobalConfiguration."], "instructions": [{"name": "PlaceMarketOrder", "docs": ["Places an immediate-or-cancel order that consumes resting liquidity on the specified market."], "discriminator": [90, 118, 192, 252, 192, 99, 39, 145], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the market order."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}]}, {"name": "PlaceLimitOrder", "docs": ["Places a limit order that may rest on the orderbook or take liquidity depending on matching conditions."], "discriminator": [108, 176, 33, 186, 146, 229, 1, 197], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the limit order."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}]}, {"name": "PlaceMultiLimitOrder", "docs": ["Places multiple post-only limit orders in a single transaction, allowing batch order placement for market making."], "discriminator": [236, 208, 221, 172, 141, 226, 129, 84], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the batch of limit orders."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "MultipleOrderPacket"}}}]}, {"name": "CancelOrdersById", "docs": ["Cancels a specific set of resting orders identified by their FIFO order ids."], "discriminator": [234, 204, 126, 94, 222, 22, 141, 24], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderIds", "type": {"defined": {"name": "OrderIds"}}}]}, {"name": "CancelUpTo", "docs": ["Cancels resting orders on the specified side up to an optional order or price threshold."], "discriminator": [26, 209, 244, 253, 59, 175, 227, 54], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelUpToInstruction"}}}]}, {"name": "CancelAll", "docs": ["Cancels every resting order owned by the connected trader on the specified market."], "discriminator": [98, 191, 75, 220, 115, 40, 71, 237], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": []}, {"name": "UpdateOraclePricesWithOrdering", "docs": ["Updates oracle-provided spot and perp prices for one or more perpetual markets with monotonic timestamp ordering."], "discriminator": [164, 0, 14, 250, 58, 198, 25, 24], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA; provides oracle authority and quote decimals for conversions."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Wallet authorised to submit oracle price updates (root oracle or delegated user)."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account recording oracle capabilities and remaining allowances."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perpetual asset map storing mark price state and oracle metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateOraclePricesWithOrderingInstruction"}}}]}, {"name": "UpdateSplinePrice", "docs": ["Updates the mid-price for a spline market-making program on the specified market."], "discriminator": [45, 155, 200, 52, 37, 214, 187, 140], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Authority of the spline trader account authorised to update the spline price."]}, {"name": "traderAccount", "docs": ["Spline trader account whose price is being updated."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection PDA containing the spline configuration and pricing data."]}, {"name": "orderbook", "optional": true, "docs": ["Optional market orderbook account included by some clients as trailing metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplinePriceParamsWithOrdering"}}}]}, {"name": "DepositFunds", "docs": ["Deposits collateral tokens from a trader's token account into the global vault, crediting the trader's account."], "discriminator": [202, 39, 52, 211, 53, 20, 250, 88], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Authority of the trader account making the deposit."]}, {"name": "traderTokenAccount", "writable": true, "docs": ["Trader's token account from which funds are transferred."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA that receives the collateral credit."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account that receives the deposited tokens."]}, {"name": "tokenProgram", "docs": ["SPL Token program for executing the transfer."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "DepositFundsInstruction"}}}]}, {"name": "RegisterTrader", "docs": ["Registers a new trader account with the specified configuration and position capacity."], "discriminator": [75, 243, 224, 167, 1, 5, 51, 32], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that validates the trader registration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Account paying for the trader account creation and rent exemption."]}, {"name": "traderWallet", "docs": ["Authority that will control the trader account; used to derive the trader PDA."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA to be created; derived from [\"trader\", trader_wallet, [trader_pda_index, trader_subaccount_index]]."], "pda": {"seeds": [{"kind": "const", "value": [116, 114, 97, 100, 101, 114]}, {"kind": "account", "path": "traderWallet"}, {"kind": "arg", "path": "params.traderPdaIndex"}, {"kind": "arg", "path": "params.traderSubaccountIndex"}]}}, {"name": "systemProgram", "docs": ["System program for creating the trader account."]}], "args": [{"name": "params", "type": {"defined": {"name": "RegisterTraderParams"}}}]}, {"name": "LiquidateViaMarketOrder", "docs": ["Liquidates a trader's position by executing an immediate-or-cancel market order to reduce their exposure."], "discriminator": [251, 241, 184, 108, 70, 70, 127, 198], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and liquidation is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "liquidatorWallet", "signer": true, "docs": ["Liquidator authority executing the liquidation."]}, {"name": "liquidatedTrader", "writable": true, "docs": ["Trader account being liquidated (must be in liquidatable state)."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "LiquidationParams"}}}]}, {"name": "LiquidationTransfer", "docs": ["Transfers positions directly between a liquidator and liquidatee once the liquidatee is flagged as unhealthy."], "discriminator": [57, 218, 215, 68, 174, 23, 204, 127], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that owns the global trader index, active trader buffer, and collateral controls."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits, pricing, and funding parameters for every listed market."]}, {"name": "liquidator", "signer": true, "docs": ["Authority of the liquidator who receives transferred positions."]}, {"name": "liquidatorAccount", "writable": true, "docs": ["Liquidator trader account whose balances are credited as transfers are applied."]}, {"name": "liquidateeAccount", "writable": true, "docs": ["Liquidatee trader account whose unhealthy positions are reduced."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "LiquidationTransferParams"}}}]}, {"name": "CloseMatchedPositions", "docs": ["Closes offsetting positions between an at-loss trader and an in-profit trader during ADL."], "discriminator": [112, 174, 32, 120, 93, 113, 10, 161], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that authorizes ADL operations and tracks trader index PDAs."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account containing risk parameters used to validate the ADL transfer."]}, {"name": "caller", "signer": true, "docs": ["ADL authority invoking the matched close instruction."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "atLossAccount", "writable": true, "docs": ["Trader account currently at a loss whose matched exposure will be closed."]}, {"name": "inProfitAccount", "writable": true, "docs": ["Trader account with offsetting profitable exposure to receive the transfer."]}], "args": [{"name": "params", "type": {"defined": {"name": "ClosePositionsParams"}}}]}, {"name": "Log", "docs": ["Internal instruction used by the Phoenix log authority to emit events on-chain.", "This instruction is called via CPI and should not be invoked directly."], "discriminator": [141, 230, 214, 242, 9, 209, 207, 170], "accounts": [{"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA that must sign via CPI."], "signer": true, "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}], "args": [{"name": "eventBatch", "docs": ["Borsh event batch containing the batch index and MarketEvent vector."], "type": {"defined": {"name": "OffChainMarketEvent"}}}]}, {"name": "LogEventLengths", "docs": ["Internal instruction used by the Phoenix log authority to emit per-event byte lengths on-chain.", "This instruction is called via CPI and should not be invoked directly."], "discriminator": [247, 7, 134, 203, 181, 71, 153, 71], "accounts": [{"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA that must sign via CPI."], "signer": true, "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}], "args": [{"name": "eventLengths", "docs": ["Borsh event-length batch containing the batch index and serialized event lengths."], "type": {"defined": {"name": "OffChainMarketEventLengths"}}}]}, {"name": "WithdrawFunds", "docs": ["Withdraws collateral tokens from the global vault to a trader's token account, debiting the trader's account balance."], "discriminator": [241, 36, 29, 111, 208, 31, 104, 217], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Authority of the trader account initiating the withdrawal."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA from which collateral is debited."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map for margin and risk checks."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account from which tokens are withdrawn."]}, {"name": "destinationTokenAccount", "writable": true, "docs": ["Trader's destination token account to receive the withdrawn tokens."]}, {"name": "tokenProgram", "docs": ["SPL Token program for executing the transfer."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "withdrawQueue", "writable": true, "docs": ["Withdraw queue PDA tracking pending withdrawal requests."]}], "args": [{"name": "params", "type": {"defined": {"name": "WithdrawFundsInstruction"}}}]}, {"name": "UpdateSplineParameters", "docs": ["Updates spline curve parameters for automated market making on bid and ask sides."], "discriminator": [241, 91, 139, 170, 18, 41, 203, 22], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Authority authorized to update spline parameters."]}, {"name": "traderAccount", "docs": ["Trader account associated with the spline."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account holding bid and ask curve parameters."]}, {"name": "orderbook", "optional": true, "docs": ["Optional market orderbook account included by some clients as trailing metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplineParametersParamsWithOrdering"}}}]}, {"name": "PlaceStopLoss", "docs": ["Places a stop-loss order that triggers when the market price reaches the specified trigger price."], "discriminator": [236, 46, 164, 66, 235, 92, 162, 137], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer for creating the stop loss account."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet authority."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account PDA to be created or updated."]}, {"name": "systemProgram", "docs": ["System program for creating the stop loss account."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceStopLossInstruction"}}}]}, {"name": "ExecuteStopLoss", "docs": ["Executes a stop-loss order when the trigger conditions are met."], "discriminator": [38, 122, 90, 90, 182, 241, 7, 4], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signer", "signer": true, "docs": ["Signer authorized to execute stop loss orders."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account verifying execution authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "funder", "writable": true, "docs": ["Account to receive rent from closed stop loss account."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to execute and close."]}], "args": [{"name": "params", "type": {"defined": {"name": "ExecuteStopLossInstruction"}}}]}, {"name": "CancelStopLoss", "docs": ["Cancels a pending stop-loss order and closes the stop loss account."], "discriminator": [120, 201, 10, 102, 12, 9, 111, 126], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "funder", "writable": true, "docs": ["Account to receive rent from closed stop loss account."]}, {"name": "traderAccount", "docs": ["Trader account associated with the stop loss order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet authority."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to cancel and close."]}, {"name": "systemProgram", "docs": ["System program for closing the account."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelStopLossInstruction"}}}]}, {"name": "ForceCancelAll", "docs": ["Force cancels all open orders for a trader account. Requires authority authorization."], "discriminator": [201, 0, 188, 127, 41, 55, 80, 48], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority authorized to force cancel orders."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": []}, {"name": "ForceCancelRiskIncreasing", "docs": ["Force cancels risk-increasing orders for a trader to reduce margin exposure."], "discriminator": [41, 55, 217, 35, 92, 72, 42, 238], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "ForceCancelRiskIncreasingParams"}}}]}, {"name": "UpdateTraderState", "docs": ["Settles funding for a trader and evicts them from the active trader buffer when they no longer have resting orders."], "discriminator": [249, 139, 82, 44, 126, 66, 133, 220], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Read-only global configuration PDA used to verify the perp asset map and buffer keys."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA whose funding snapshot and active status are updated."]}, {"name": "perpAssetMap", "docs": ["Perp asset map providing funding parameters for settlement."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": []}, {"name": "ConsumeWithdrawQueue", "docs": ["Processes pending withdrawals from the withdraw queue."], "discriminator": [228, 40, 211, 139, 39, 212, 116, 113], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "withdrawQueue", "writable": true, "docs": ["Withdraw queue account."]}, {"name": "tokenProgram", "docs": ["SPL Token program."]}, {"name": "traderWallet", "docs": ["Trader wallet receiving the withdrawal."]}, {"name": "destinationTokenAccount", "writable": true, "docs": ["Destination token account for withdrawn funds."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}], "args": []}, {"name": "CreatePermission", "docs": ["Creates a new permission account PDA for delegating authority."], "discriminator": [190, 182, 26, 164, 156, 221, 8, 0], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer for account creation rent."]}, {"name": "permission", "writable": true, "docs": ["Permission account PDA to create."]}, {"name": "authority", "docs": ["Authority granting the permission."]}, {"name": "user", "docs": ["User receiving the permission."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "DelegateTrader", "docs": ["Delegates position authority of a trader account to another address."], "discriminator": [3, 164, 192, 106, 119, 91, 122, 224], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Current trader authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA to delegate."]}, {"name": "newPositionAuthority", "docs": ["New position authority address."]}], "args": []}, {"name": "RevokePermission", "docs": ["Revokes permission flags from a permission account."], "discriminator": [116, 82, 33, 181, 121, 144, 249, 227], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority revoking the permission."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission account for delegation."]}, {"name": "targetPermission", "writable": true, "docs": ["Permission account to revoke flags from."]}], "args": [{"name": "params", "type": {"defined": {"name": "RevokePermissionParams"}}}]}, {"name": "SetPermission", "docs": ["Sets permission flags on a permission account."], "discriminator": [70, 126, 41, 194, 245, 189, 128, 8], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "permission", "writable": true, "docs": ["Permission account to modify."]}, {"name": "authority", "signer": true, "docs": ["Authority owning the permission."]}, {"name": "user", "docs": ["User the permission applies to."]}], "args": [{"name": "params", "type": {"defined": {"name": "SetPermissionInstruction"}}}]}, {"name": "SetTraderCapabilitiesDelegated", "docs": ["Sets trader capability flags via a delegated permission."], "discriminator": [207, 170, 17, 21, 53, 35, 88, 151], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority with trader onboarding delegation."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account for delegation validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TraderCapabilityUpdate"}}}]}, {"name": "SetTraderCapability", "docs": ["Sets trader capability flags via risk authority."], "discriminator": [191, 72, 45, 190, 214, 250, 182, 213], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account for delegation validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TraderCapabilityUpdate"}}}]}, {"name": "SyncParentToChild", "docs": ["Syncs capabilities from a parent trader account to a child (isolated margin) account."], "discriminator": [175, 137, 217, 11, 235, 39, 150, 19], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "docs": ["Trader wallet owning both accounts."]}, {"name": "parentTraderAccount", "docs": ["Parent (cross margin) trader account."]}, {"name": "childTraderAccount", "writable": true, "docs": ["Child (isolated margin) trader account to sync."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}], "args": []}, {"name": "TransferCollateral", "docs": ["Transfers collateral between two trader accounts owned by the same wallet."], "discriminator": [157, 163, 63, 27, 242, 72, 251, 97], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet owning both accounts."]}, {"name": "srcTraderAccount", "writable": true, "docs": ["Source trader account."]}, {"name": "dstTraderAccount", "writable": true, "docs": ["Destination trader account."]}, {"name": "perpAssetMap", "docs": ["Perp asset map for margin calculations."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TransferCollateralInstruction"}}}]}, {"name": "TransferCollateralChildToParent", "docs": ["Transfers collateral from a child (isolated margin) account to its parent (cross margin) account."], "discriminator": [51, 100, 177, 115, 139, 135, 247, 139], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "docs": ["Trader wallet owning both accounts."]}, {"name": "childTraderAccount", "writable": true, "docs": ["Child (isolated margin) trader account."]}, {"name": "parentTraderAccount", "writable": true, "docs": ["Parent (cross margin) trader account."]}, {"name": "perpAssetMap", "docs": ["Perp asset map for margin calculations."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": []}, {"name": "UncrossCrank", "docs": ["Cranks the orderbook to uncross any crossed orders."], "discriminator": [196, 121, 94, 253, 139, 42, 252, 147], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account."]}], "args": [{"name": "params", "type": {"defined": {"name": "UncrossCrankParams"}}}]}, {"name": "ClearExpiredOrders", "docs": ["Clears expired orders from the orderbook as a maintenance crank operation."], "discriminator": [128, 95, 102, 169, 146, 205, 248, 94], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "ClearExpiredOrdersParams"}}}]}, {"name": "CreateConditionalOrdersAccount", "docs": ["Allocates the trader's conditional-orders PDA so further conditional-order placements can store entries."], "discriminator": [201, 6, 112, 61, 231, 15, 57, 248], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Funds the rent for the new conditional-orders PDA."]}, {"name": "traderWallet", "docs": ["Trader authority wallet; not signed here so a sponsorship server may pay rent."]}, {"name": "traderAccount", "docs": ["Trader state PDA whose key seeds the conditional-orders PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Empty account that will be initialised as the conditional-orders collection."]}, {"name": "systemProgram", "docs": ["System program for PDA creation."]}], "args": [{"name": "params", "type": {"defined": {"name": "CreateConditionalOrdersAccountInstruction"}}}]}, {"name": "PlacePositionConditionalOrder", "docs": ["Places a position-based conditional order that fires when mark price crosses the configured trigger."], "discriminator": [65, 108, 83, 129, 76, 193, 92, 143], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any account growth required by the conditional-orders PDA."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA; updated as part of risk validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA for the market."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet authorising the conditional order."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "systemProgram", "docs": ["System program (used for any required reallocations)."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlacePositionConditionalOrderInstruction"}}}]}, {"name": "CancelConditionalOrder", "docs": ["Cancels one or both trigger-order halves of a conditional-order entry."], "discriminator": [82, 104, 25, 51, 248, 54, 66, 184], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA owning the conditional order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelConditionalOrderInstruction"}}}]}, {"name": "PlaceAttachedConditionalOrder", "docs": ["Attaches a conditional order to an existing resting limit order so it fires when that order is filled."], "discriminator": [43, 117, 136, 128, 72, 150, 101, 122], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA owning the parent resting order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the parent limit order."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any conditional-orders PDA reallocation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "systemProgram", "docs": ["System program for PDA reallocation."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceAttachedConditionalOrderInstruction"}}}]}, {"name": "PlaceLimitOrderWithConditionals", "docs": ["Places a limit order and atomically attaches greater/less-than trigger orders to the resulting resting order."], "discriminator": [95, 45, 68, 168, 232, 218, 210, 92], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any conditional-orders PDA reallocation."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "systemProgram", "docs": ["System program for PDA reallocation."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceLimitOrderWithConditionalsInstruction"}}}]}, {"name": "ExecuteConditionalOrders", "docs": ["Authority-driven execution of a triggered conditional order."], "discriminator": [101, 214, 110, 0, 190, 226, 177, 236], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority (or delegate) authorised to execute conditional orders."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission/delegation PDA validated against the authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "ExecuteConditionalOrderInstruction"}}}]}, {"name": "PingConditionalOrders", "docs": ["Authority-driven sweep that activates or invalidates a trader's conditional orders based on current state."], "discriminator": [135, 170, 166, 34, 208, 230, 91, 64], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority (or delegate) authorised to ping conditional orders."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission/delegation PDA."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": []}, {"name": "CancelAllPlusConditional", "docs": ["Cancels every resting order for a trader on a market and clears the matching conditional orders."], "discriminator": [142, 173, 238, 126, 79, 19, 53, 103], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": []}, {"name": "MarketClosedForceCancelAll", "docs": ["Force cancels all open orders and conditional orders for a trader after a market has closed."], "discriminator": [229, 101, 143, 249, 150, 175, 175, 219], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Optional conditional orders account for the trader; clears stop loss and trigger state when present."]}], "args": []}, {"name": "ForceCancelAfterHours", "docs": ["Force cancels orders that are invalid during commodity after-hours trading."], "discriminator": [147, 13, 224, 61, 248, 234, 64, 164], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": []}, {"name": "AuthorizedForceCancel", "docs": ["Force cancels orders under the configured cancel authority until the requested side sizes remain."], "discriminator": [13, 22, 221, 234, 211, 165, 172, 2], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "cancelAuthority", "signer": true, "docs": ["Configured cancel authority signer."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "AuthorizedForceCancelParams"}}}]}, {"name": "AuthorizedForceCancelById", "docs": ["Force cancels specific bid and ask orders under the configured cancel authority."], "discriminator": [122, 159, 12, 159, 144, 108, 225, 241], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "cancelAuthority", "signer": true, "docs": ["Configured cancel authority signer."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "AuthorizedForceCancelByIdParams"}}}]}, {"name": "ChangeMarketStatus", "docs": ["Changes an orderbook market status through the market authority path."], "discriminator": [221, 127, 224, 41, 177, 145, 126, 8], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account whose market status is updated."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map used to validate and update market metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "ChangeMarketStatusParams"}}}]}, {"name": "CreateEscrowAccount", "docs": ["Creates or initializes the escrow PDA for a trader wallet."], "discriminator": [146, 147, 225, 47, 51, 64, 112, 1], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer funding escrow account creation."]}, {"name": "traderWallet", "docs": ["Trader wallet that owns the escrow account."]}, {"name": "traderEscrow", "writable": true, "docs": ["Escrow account PDA for the trader wallet."]}, {"name": "systemProgram", "docs": ["System program for creating the escrow account."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "CreateEscrowAccountParams"}}}]}, {"name": "CreateEscrowRequest", "docs": ["Creates an escrow request between sender and receiver trader accounts."], "discriminator": [116, 164, 236, 204, 187, 132, 195, 219], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "signer": true, "docs": ["Sender wallet signing the escrow request."]}, {"name": "senderTraderAccount", "docs": ["Sender trader account whose authority is validated."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account authorizing delegated escrow request creation."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverTraderAccount", "docs": ["Receiver trader account whose authority is validated."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account holding pending requests."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "CreateEscrowRequestParams"}}}]}, {"name": "AcceptEscrowRequest", "docs": ["Accepts a pending escrow request and applies its transfer actions."], "discriminator": [182, 41, 62, 227, 228, 175, 22, 137], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "docs": ["Sender wallet for the pending escrow request."]}, {"name": "senderTraderAccount", "writable": true, "docs": ["Sender trader account affected by accepted escrow actions."]}, {"name": "receiverWallet", "signer": true, "docs": ["Receiver wallet accepting the escrow request."]}, {"name": "receiverTraderAccount", "writable": true, "docs": ["Receiver trader account affected by accepted escrow actions."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account containing the pending request."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": []}, {"name": "CancelEscrowRequest", "docs": ["Cancels a pending escrow request by sequence number."], "discriminator": [202, 26, 163, 239, 32, 8, 85, 2], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerWallet", "signer": true, "docs": ["Sender or receiver wallet authorized to cancel the escrow request."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account containing the request."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelEscrowRequestParams"}}}]}, {"name": "UpsertEscrowRequest", "docs": ["Creates or increments a cash escrow request for a sender and receiver pair."], "discriminator": [213, 56, 86, 93, 34, 103, 160, 99], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "signer": true, "docs": ["Sender wallet signing the escrow upsert."]}, {"name": "senderTraderAccount", "docs": ["Sender trader account whose authority is validated."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account authorizing delegated escrow request creation."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverTraderAccount", "docs": ["Receiver trader account whose authority is validated."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account holding pending requests."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpsertEscrowRequestParams"}}}]}, {"name": "RegisterSpline", "docs": ["Registers a trader as an active spline participant for a market."], "discriminator": [30, 176, 160, 79, 252, 117, 241, 58], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "payerAccount", "writable": true, "signer": true, "docs": ["Payer funding any required spline account growth."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account for the market."]}, {"name": "marketAccount", "docs": ["Market orderbook account for the spline."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account being registered as a spline participant."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for isolated-only validation."]}, {"name": "systemProgram", "docs": ["System program for rent transfers or account growth."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "RegisterSplineParams"}}}]}, {"name": "UpdateCommodityMarketState", "docs": ["Updates commodity market state and related mark price inputs."], "discriminator": [45, 245, 97, 184, 200, 147, 142, 73], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or commodity-market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account updated with commodity market state."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account for the commodity market."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for the commodity market."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateCommodityMarketStateInstruction"}}}]}, {"name": "UpdatePerpIsolatedOnly", "docs": ["Updates the isolated-only flag for a perp asset."], "discriminator": [101, 56, 163, 250, 73, 35, 115, 91], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpIsolatedOnlyInstruction"}}}]}, {"name": "UpdatePerpLeverageTiers", "docs": ["Updates leverage tiers for a perp asset."], "discriminator": [110, 223, 244, 99, 34, 83, 97, 121], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpLeverageTiersInstruction"}}}]}, {"name": "UpdatePerpOpenInterestCap", "docs": ["Updates open interest cap for a perp asset."], "discriminator": [75, 137, 116, 5, 80, 123, 107, 177], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpOpenInterestCapInstruction"}}}]}, {"name": "UpdatePerpUPnlRiskFactor", "docs": ["Updates uPnL risk factor for a perp asset."], "discriminator": [113, 214, 226, 254, 147, 9, 94, 220], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpUPnlRiskFactorInstruction"}}}]}, {"name": "UpdatePerpUPnlRiskFactorForWithdrawals", "docs": ["Updates withdrawal uPnL risk factor for a perp asset."], "discriminator": [252, 32, 10, 88, 238, 245, 104, 59], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpUPnlRiskFactorForWithdrawalsInstruction"}}}]}, {"name": "UpdatePerpCancelRiskFactor", "docs": ["Updates cancel-order risk factor for a perp asset."], "discriminator": [88, 6, 185, 205, 118, 28, 129, 242], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpCancelRiskFactorInstruction"}}}]}, {"name": "UpdatePerpMarkPriceParameters", "docs": ["Updates mark price calculation parameters for a perp asset."], "discriminator": [254, 86, 13, 94, 43, 224, 160, 226], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpMarkPriceParametersInstruction"}}}]}, {"name": "UpdateTraderFees", "docs": ["Updates fee override multipliers for a trader."], "discriminator": [182, 233, 57, 205, 174, 168, 73, 237], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or fee-update delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account whose fee overrides are updated."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateTraderFeesParams"}}}]}, {"name": "UpdateMarketFees", "docs": ["Updates default fee parameters for a market orderbook."], "discriminator": [187, 36, 121, 171, 131, 19, 243, 117], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbookAccount", "writable": true, "docs": ["Orderbook account whose default fee parameters are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateMarketFeesParams"}}}]}, {"name": "UpdateWithdrawRateLimits", "docs": ["Updates withdrawal throttle budget and replenishment parameters."], "discriminator": [187, 246, 146, 182, 141, 216, 36, 1], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority or withdrawal-suspension delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "withdrawQueueAccount", "writable": true, "docs": ["Withdraw queue account whose throttle settings are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateWithdrawRateLimitsParams"}}}]}, {"name": "UpdateWithdrawParameters", "docs": ["Updates withdraw cooldown and fee parameters."], "discriminator": [87, 209, 225, 132, 91, 151, 249, 132], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "withdrawQueueAccount", "writable": true, "docs": ["Withdraw queue account whose fee parameters are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateWithdrawParametersParams"}}}]}, {"name": "UpdateAuthorities", "docs": ["Updates subordinate authorities stored in global configuration."], "discriminator": [175, 228, 137, 18, 175, 70, 220, 165], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority or permitted delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateAuthoritiesParams"}}}]}, {"name": "InitializeArena", "docs": ["Initializes a new global trader index or active trader buffer arena account."], "discriminator": [11, 37, 221, 1, 205, 120, 25, 230], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority or arena-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "headerAccount", "writable": true, "docs": ["Arena family header account."]}, {"name": "newArenaAccount", "writable": true, "docs": ["New arena account to create and initialize."]}, {"name": "systemProgram", "docs": ["System program for creating the arena account."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "InitializeArenaParams"}}}]}, {"name": "RemoveEmptyArena", "docs": ["Removes an empty active trader buffer arena account."], "discriminator": [155, 65, 162, 79, 211, 221, 197, 168], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority or arena-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "headerAccount", "writable": true, "docs": ["Active trader buffer header account."]}, {"name": "removedAccount", "writable": true, "docs": ["Empty arena account to remove."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "RemoveOracle", "docs": ["Removes one or more oracle public keys from the perp asset map."], "discriminator": [60, 93, 51, 197, 182, 42, 170, 26], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Oracle authority or oracle-update delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "RemoveOracleInstruction"}}}]}, {"name": "RemoveAllOracles", "docs": ["Removes all oracle public keys for a perp asset."], "discriminator": [164, 64, 91, 210, 137, 189, 135, 38], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "RemoveAllOraclesInstruction"}}}]}, {"name": "SetPermissionDelegated", "docs": ["Sets a whitelisted permission on a target permission account through delegated authority."], "discriminator": [35, 25, 57, 68, 255, 26, 161, 229], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority with permission to set delegated permissions."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "targetPermission", "writable": true, "docs": ["Permission account whose flags are updated."]}, {"name": "from", "docs": ["Authority seed of the target permission PDA."]}, {"name": "to", "docs": ["User seed of the target permission PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "SetPermissionInstruction"}}}]}, {"name": "DeactivateSpline", "docs": ["Deactivates a trader spline for a market."], "discriminator": [0, 254, 22, 100, 238, 197, 118, 181], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account for the market."]}, {"name": "marketAccount", "docs": ["Market orderbook account for the spline."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account being deactivated as a spline participant."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used while updating trader activity."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "ForceCancelStopLoss", "docs": ["Force cancels stop loss state for a trader after market closure."], "discriminator": [114, 230, 115, 170, 66, 79, 19, 172], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "docs": ["Closed market orderbook associated with the stop loss."]}, {"name": "funder", "writable": true, "docs": ["Funder account receiving reclaimed stop loss rent."]}, {"name": "traderAccount", "docs": ["Trader account that owns the stop loss."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to close."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "UpdateFundingParameters", "docs": ["Updates funding parameters for a perp asset."], "discriminator": [101, 195, 104, 104, 116, 20, 223, 188], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateFundingParametersInstruction"}}}]}, {"name": "CloseMarket", "docs": ["Closes a market and records its finalized mark price."], "discriminator": [88, 154, 248, 186, 48, 14, 123, 244], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account updated with finalized mark price."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account whose status is moved to Closed."]}], "args": []}, {"name": "TombstoneMarket", "docs": ["Tombstones a closed market after open interest, orders, and splines are cleared."], "discriminator": [90, 177, 17, 88, 85, 11, 177, 96], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "writable": true, "docs": ["Closed orderbook account to tombstone."]}, {"name": "perpAssetMap", "docs": ["Perp asset map account used to verify open interest is zero."]}, {"name": "splineCollection", "docs": ["Spline collection account used to verify no active splines remain."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": []}, {"name": "SettleClosedMarketPosition", "docs": ["Settles matching long and short trader positions in a closed market."], "discriminator": [27, 222, 221, 233, 136, 15, 254, 210], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account containing finalized mark price."]}, {"name": "orderbook", "writable": true, "docs": ["Closed orderbook account for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "longTrader", "writable": true, "docs": ["Trader account with a long position to settle."]}, {"name": "shortTrader", "writable": true, "docs": ["Trader account with a short position to settle."]}], "args": []}, {"name": "DeleteTombstonedMarket", "docs": ["Deletes a tombstoned market and reclaims orderbook and spline rent."], "discriminator": [146, 17, 252, 116, 222, 113, 52, 102], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "lamportRecipient", "writable": true, "docs": ["Account receiving reclaimed lamports."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account from which the market metadata is removed."]}, {"name": "orderbook", "writable": true, "docs": ["Tombstoned orderbook account to delete."]}, {"name": "splineCollection", "writable": true, "docs": ["Spline collection account to delete."]}], "args": []}, {"name": "UpdateSplinePositionLimitsConfig", "docs": ["Updates position-size limits and leverage decrease configuration for a spline trader."], "discriminator": [96, 60, 5, 112, 19, 144, 13, 107], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Trader or authority signer updating spline position limits."]}, {"name": "traderAccount", "docs": ["Trader account associated with the spline."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account holding the trader spline."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplinePositionLimitsConfigParams"}}}]}], "accounts": [{"name": "GlobalConfiguration", "discriminator": [37, 146, 212, 210, 47, 136, 111, 20], "docs": ["Global exchange configuration account derived from the \"global\" PDA seed.", "Stores configured authorities, canonical collateral mint, global vault, PerpAssetMap, GlobalTraderIndex, ActiveTraderBuffer, and WithdrawQueue addresses."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}]}}, {"name": "OrderbookHeader", "discriminator": [65, 1, 40, 241, 166, 14, 86, 188], "docs": ["Market orderbook account header for a perpetual market.", "Market orderbooks are explicit market accounts created during AddMarket and recorded in PerpAssetMap metadata; they are not PDAs."]}, {"name": "SplineCollectionHeader", "discriminator": [192, 245, 182, 191, 88, 37, 79, 48], "docs": ["Spline collection account header derived from [\"spline\", orderbook].", "Stores spline market-making state for one market."], "pda": {"seeds": [{"kind": "const", "value": [115, 112, 108, 105, 110, 101]}, {"kind": "account", "path": "marketAccount", "account": "OrderbookHeader"}]}}, {"name": "Trader", "discriminator": [41, 97, 73, 105, 110, 214, 112, 9], "docs": ["Trader account derived from [\"trader\", authority, trader_pda_index, trader_subaccount_index].", "Stores collateral, positions, open orders, and per-trader risk state."], "pda": {"seeds": [{"kind": "const", "value": [116, 114, 97, 100, 101, 114]}, {"kind": "account", "path": "authority"}, {"kind": "arg", "path": "traderPdaIndex"}, {"kind": "arg", "path": "traderSubaccountIndex"}]}}, {"name": "ActiveTraderBufferHeader", "discriminator": [192, 255, 205, 165, 80, 154, 131, 5], "docs": ["Header arena for the active trader buffer derived from [\"active_trader_buffer\", 0u16]."], "pda": {"seeds": [{"kind": "const", "value": [97, 99, 116, 105, 118, 101, 95, 116, 114, 97, 100, 101, 114, 95, 98, 117, 102, 102, 101, 114]}, {"kind": "const", "value": [0, 0]}]}}, {"name": "ActiveTraderBufferArenaHeader", "discriminator": [6, 249, 57, 137, 82, 121, 191, 107], "docs": ["Additional active trader buffer arena derived from [\"active_trader_buffer\", arena_index]."], "pda": {"seeds": [{"kind": "const", "value": [97, 99, 116, 105, 118, 101, 95, 116, 114, 97, 100, 101, 114, 95, 98, 117, 102, 102, 101, 114]}, {"kind": "arg", "path": "arenaIndex"}]}}, {"name": "GlobalTraderIndexHeader", "discriminator": [145, 92, 169, 6, 5, 144, 1, 205], "docs": ["Header arena for the global trader index derived from [\"global_trader_index\", 0u16]."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108, 95, 116, 114, 97, 100, 101, 114, 95, 105, 110, 100, 101, 120]}, {"kind": "const", "value": [0, 0]}]}}, {"name": "GlobalTraderIndexArenaHeader", "discriminator": [98, 110, 179, 71, 203, 120, 200, 235], "docs": ["Additional global trader index arena derived from [\"global_trader_index\", arena_index]."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108, 95, 116, 114, 97, 100, 101, 114, 95, 105, 110, 100, 101, 120]}, {"kind": "arg", "path": "arenaIndex"}]}}, {"name": "PerpAssetMap", "discriminator": [142, 54, 115, 223, 124, 239, 108, 38], "docs": ["Perpetual asset map account storing market metadata, risk parameters, oracle metadata, and funding state.", "The account pubkey is stored in GlobalConfiguration rather than derived from a fixed PDA seed; it is not a PDA."]}, {"name": "WithdrawQueueHeader", "discriminator": [125, 59, 94, 108, 171, 16, 43, 228], "docs": ["Withdraw queue account storing pending withdrawals and withdrawal throttle state.", "The account pubkey is stored in GlobalConfiguration rather than derived from a fixed PDA seed; it is not a PDA."]}, {"name": "PermissionAccount", "discriminator": [180, 172, 109, 215, 129, 165, 97, 38], "docs": ["Permission delegation account derived from [\"permission\", authority, user].", "Stores delegated capability bits and optional expiry/usage constraints."], "pda": {"seeds": [{"kind": "const", "value": [112, 101, 114, 109, 105, 115, 115, 105, 111, 110]}, {"kind": "account", "path": "authority"}, {"kind": "account", "path": "user"}]}}, {"name": "StopLosses", "discriminator": [127, 64, 139, 143, 70, 43, 167, 152], "docs": ["Stop-loss account derived from [\"stoploss\", trader_account, asset_id].", "Stores trigger orders for one trader and market."], "pda": {"seeds": [{"kind": "const", "value": [115, 116, 111, 112, 108, 111, 115, 115]}, {"kind": "account", "path": "traderAccount", "account": "Trader"}, {"kind": "arg", "path": "assetId"}]}}, {"name": "EscrowHeader", "discriminator": [50, 4, 48, 59, 209, 138, 251, 204], "docs": ["Escrow inbox account derived from [\"escrow\", authority].", "Stores pending escrow requests for the authority."], "pda": {"seeds": [{"kind": "const", "value": [101, 115, 99, 114, 111, 119]}, {"kind": "account", "path": "authority"}]}}, {"name": "ConditionalOrderHeader", "discriminator": [147, 116, 229, 177, 249, 200, 146, 49], "docs": ["Conditional orders account derived from [\"conditional_orders\", trader_account].", "Stores attached and position-scoped conditional orders for a trader account."], "pda": {"seeds": [{"kind": "const", "value": [99, 111, 110, 100, 105, 116, 105, 111, 110, 97, 108, 95, 111, 114, 100, 101, 114, 115]}, {"kind": "account", "path": "traderAccount", "account": "Trader"}]}}, {"name": "DirectMarkOverride", "discriminator": [142, 54, 115, 223, 124, 239, 108, 38]}, {"name": "ReferencePriceOverride", "discriminator": [210, 123, 24, 82, 132, 17, 53, 94]}], "types": [{"name": "OrderPacket", "docs": ["Serialized parameters for Phoenix order placement instructions."], "type": {"kind": "struct", "fields": [{"name": "kind", "type": {"defined": {"name": "OrderPacketKind"}}}]}}, {"name": "OrderPacketKind", "docs": ["Supported order variants accepted by the Phoenix matching engine."], "type": {"kind": "enum", "variants": [{"name": "PostOnly", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "slide", "type": "bool"}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}, {"name": "Limit", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "selfTradeBehavior", "type": {"defined": {"name": "SelfTradeBehavior"}}}, {"name": "matchLimit", "type": {"option": "u64"}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}, {"name": "ImmediateOrCancel", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "numQuoteLots", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "minBaseLotsToFill", "type": {"defined": {"name": "BaseLots"}}}, {"name": "minQuoteLotsToFill", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "selfTradeBehavior", "type": {"defined": {"name": "SelfTradeBehavior"}}}, {"name": "matchLimit", "type": {"option": "u64"}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}]}}, {"name": "Side", "docs": ["Trading direction for an order."], "type": {"kind": "enum", "variants": [{"name": "Bid"}, {"name": "Ask"}]}}, {"name": "SelfTradeBehavior", "docs": ["Policy for handling matches against the submitting trader's resting orders."], "type": {"kind": "enum", "variants": [{"name": "Abort"}, {"name": "CancelProvide"}, {"name": "DecrementTake"}]}}, {"name": "BaseLots", "docs": ["Quantity wrapper representing discrete base lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "QuoteLots", "docs": ["Quantity wrapper representing discrete quote lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "Ticks", "docs": ["Discrete price unit measured in quote lots per base lot."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "BaseLotsPerTick", "docs": ["Density of liquidity per tick for spline market making."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OrderFlags", "docs": ["Bitmask flags applied to an order (currently only the reduce-only bit is used)."], "type": {"kind": "struct", "fields": [{"name": "flags", "type": "u8"}]}}, {"name": "OrderIds", "docs": ["Collection of cancel identifiers used to target specific resting orders."], "type": {"kind": "struct", "fields": [{"name": "orderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}]}}, {"name": "CancelUpToInstruction", "docs": ["Parameters controlling cancel-up-to behaviour."], "type": {"kind": "struct", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "numOrdersToCancel", "type": {"option": "u64"}}, {"name": "tickLimit", "type": {"option": "u64"}}]}}, {"name": "CancelId", "docs": ["Identifies a resting order along with an optional hint into the orderbook."], "type": {"kind": "struct", "fields": [{"name": "nodePointer", "type": {"defined": {"name": "NodePointer"}}}, {"name": "orderId", "type": {"defined": {"name": "FIFOOrderId"}}}]}}, {"name": "FIFOOrderId", "docs": ["Stable identifier for a resting order composed of price ticks and sequence number."], "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "NodePointer", "docs": ["Pointer hint into the orderbook linked list; zero represents a null pointer."], "type": {"kind": "struct", "fields": [{"name": "value", "type": "u32"}]}}, {"name": "Symbol", "docs": ["Fixed-width (16 byte) asset symbol stored as UTF-8 with trailing zero padding."], "type": {"kind": "struct", "fields": [{"name": "symbolBytes", "type": {"array": ["u8", 16]}}]}}, {"name": "Price", "docs": ["Price representation composed of a mantissa (`value`) and decimal exponent (`expo`)."], "type": {"kind": "struct", "fields": [{"name": "value", "type": "u64"}, {"name": "expo", "type": "u8"}]}}, {"name": "OraclePriceUpdate", "docs": ["Single oracle price update targeting a specific perpetual asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetId", "type": {"defined": {"name": "Symbol"}}}, {"name": "newExchangePerpPrice", "type": {"option": {"defined": {"name": "Price"}}}}, {"name": "newExchangeSpotPrice", "type": {"defined": {"name": "Price"}}}]}}, {"name": "UpdateOraclePricesWithOrderingInstruction", "docs": ["Oracle instruction payload containing ordered price updates and timestamp reset policy."], "type": {"kind": "struct", "fields": [{"name": "updateTimestamp", "type": "u64"}, {"name": "updates", "type": {"vec": {"defined": {"name": "OraclePriceUpdate"}}}}, {"name": "shouldResetTimestamp", "type": "bool"}]}}, {"name": "UpdateSplinePriceParams", "docs": ["Parameters for updating a spline's mid-price."], "type": {"kind": "struct", "fields": [{"name": "newMidPrice", "type": "u64"}, {"name": "userUpdateSlot", "type": {"option": "u64"}}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "UpdateSplinePriceParamsWithOrdering", "docs": ["Parameters for updating a spline's mid-price with anti-reordering protection."], "type": {"kind": "struct", "fields": [{"name": "newMidPrice", "type": "u64"}, {"name": "userUpdateSlot", "type": {"option": "u64"}}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "overrideSequenceNumber", "type": "bool"}]}}, {"name": "DepositFundsInstruction", "docs": ["Parameters for depositing collateral into the global vault."], "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "RegisterTraderParams", "docs": ["Parameters for registering a new trader account."], "type": {"kind": "struct", "fields": [{"name": "maxPositions", "type": "u64"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}]}}, {"name": "LiquidationParams", "docs": ["Parameters for liquidating a trader's position via market order."], "type": {"kind": "struct", "fields": [{"name": "assetMint", "type": "pubkey"}, {"name": "liquidationSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "liquidationPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "fillOrKill", "type": "bool"}]}}, {"name": "TransferRequest", "docs": ["Transfer payload describing how many base lots of an asset should move from the liquidatee to the liquidator."], "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u64"}, {"name": "amount", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "LiquidationTransferParams", "docs": ["Parameters controlling liquidation transfers between traders."], "type": {"kind": "struct", "fields": [{"name": "transferRequests", "type": {"vec": {"defined": {"name": "TransferRequest"}}}}, {"name": "executeAllTransfers", "type": "bool"}]}}, {"name": "ClosePositionsParams", "docs": ["Parameters for closing matched positions between at-loss and in-profit traders."], "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u64"}, {"name": "amount", "type": {"defined": {"name": "BaseLots"}}}, {"name": "atLossCloseValue", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "inProfitCloseValue", "type": {"option": {"defined": {"name": "QuoteLots"}}}}]}}, {"name": "MultipleOrderPacket", "docs": ["Batch order packet for placing multiple post-only limit orders in a single transaction."], "type": {"kind": "struct", "fields": [{"name": "bids", "type": {"vec": {"defined": {"name": "CondensedOrder"}}}}, {"name": "asks", "type": {"vec": {"defined": {"name": "CondensedOrder"}}}}, {"name": "clientOrderId", "type": {"option": {"array": ["u8", 16]}}}, {"name": "slide", "type": "bool"}]}}, {"name": "CondensedOrder", "docs": ["Condensed order representation for batch order placement (price, size, and optional TiF)."], "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": "u64"}, {"name": "sizeInBaseLots", "type": "u64"}, {"name": "lastValidSlot", "type": {"option": "u64"}}]}}, {"name": "WithdrawFundsInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64", "docs": ["Amount in quote lots to withdraw."]}]}}, {"name": "UpdateSplineParametersParams", "type": {"kind": "struct", "fields": [{"name": "bidRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "askRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "UpdateSplineParametersParamsWithOrdering", "type": {"kind": "struct", "fields": [{"name": "bidRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "askRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "overrideSequenceNumber", "type": "bool"}]}}, {"name": "TickRegionParams", "type": {"kind": "struct", "fields": [{"name": "startOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "endOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "density", "type": {"defined": {"name": "BaseLotsPerTick"}}}, {"name": "lifespan", "type": "u64"}]}}, {"name": "PlaceStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "triggerPrice", "type": "u64"}, {"name": "executionPrice", "type": "u64"}, {"name": "tradeSize", "type": "u64"}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}]}}, {"name": "ExecuteStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "CancelStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "ForceCancelRiskIncreasingParams", "type": {"kind": "struct", "fields": []}}, {"name": "Direction", "type": {"kind": "enum", "variants": [{"name": "GreaterThan"}, {"name": "LessThan"}]}}, {"name": "StopLossOrderKind", "type": {"kind": "enum", "variants": [{"name": "IOC"}, {"name": "Limit"}]}}, {"name": "LogHeader", "type": {"kind": "struct", "fields": [{"name": "logBatchIndex", "docs": ["Index of this log batch for tracking event sequences."], "type": "u32"}, {"name": "totalEvents", "docs": ["Number of events serialized in this log batch."], "type": "u32"}]}}, {"name": "OrderbookHeader", "docs": ["Fixed metadata prefix for a Phoenix Eternal orderbook account.", "The large recent-trade buffer and dynamic orderbook bytes follow this prefix and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "marketStatus", "type": "u8"}, {"name": "baseLotsDecimals", "type": "i8"}, {"name": "padding0", "type": {"array": ["u8", 6]}}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "assetId", "type": "u32"}, {"name": "assetIdPadding", "type": {"array": ["u8", 4]}}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "tickSizeInQuoteLotsPerBaseLot", "type": "u64"}, {"name": "orderSequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "tradeSequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "defaultTakerFeeMicro", "type": "u32"}, {"name": "defaultMakerFeeMicro", "type": "i32"}, {"name": "totalMakerQuoteLotFees", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "totalTakerQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}]}}, {"name": "SplineCollectionHeader", "docs": ["Fixed SplineCollectionHeader prefix for a Phoenix Eternal spline collection account.", "Dynamic spline map bytes follow this header and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "market", "type": "pubkey"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "numSplines", "type": "u32"}, {"name": "numActive", "type": "u32"}, {"name": "padding", "type": {"array": ["u8", 32]}}]}}, {"name": "TraderState", "docs": ["Fixed trader state embedded in the Phoenix Eternal DynamicTraderHeader."], "type": {"kind": "struct", "fields": [{"name": "quoteLotCollateral", "type": "i64"}, {"name": "flags", "type": "u32"}, {"name": "padding", "type": {"array": ["u8", 1]}}, {"name": "globalPositionSequenceNumber", "type": "u8"}, {"name": "makerFeeOverrideMultiplier", "type": "i8"}, {"name": "takerFeeOverrideMultiplier", "type": "i8"}]}}, {"name": "Trader", "docs": ["Fixed DynamicTraderHeader prefix for a Phoenix Eternal trader account.", "Dynamic position map bytes follow this header and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "key", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "traderState", "type": {"defined": {"name": "TraderState"}}}, {"name": "padding0", "type": {"array": ["u8", 4]}}, {"name": "withdrawQueueNode", "type": "u32"}, {"name": "maxPositions", "type": "u64"}, {"name": "positionAuthority", "type": "pubkey"}, {"name": "numMarketsWithSplines", "type": "u16"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "padding1", "type": {"array": ["u8", 4]}}, {"name": "lastDepositSlot", "type": "u64"}, {"name": "conditionalOrderBits", "type": {"array": ["u8", 24]}}]}}, {"name": "ActiveTraderBufferHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full active trader buffer layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "ActiveTraderBufferArenaHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full active trader buffer arena layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalTraderIndexHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full global trader index layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalTraderIndexArenaHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full global trader index arena layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalConfiguration", "type": {"kind": "struct", "fields": [{"name": "accountKey", "type": "pubkey"}, {"name": "rootAuthority", "type": "pubkey"}, {"name": "riskAuthority", "type": "pubkey"}, {"name": "marketAuthority", "type": "pubkey"}, {"name": "oracleAuthority", "type": "pubkey"}, {"name": "successorAuthority", "type": "pubkey"}, {"name": "adlAuthority", "type": "pubkey"}, {"name": "cancelAuthority", "type": "pubkey"}, {"name": "backstopAuthority", "type": "pubkey"}, {"name": "canonicalTokenMintKey", "type": "pubkey"}, {"name": "globalVaultKey", "type": "pubkey"}, {"name": "perpAssetMapKey", "type": "pubkey"}, {"name": "globalTraderIndexHeaderKey", "type": "pubkey"}, {"name": "activeTraderBufferHeaderKey", "type": "pubkey"}, {"name": "totalQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "unclaimedQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "withdrawQueueKey", "type": "pubkey"}, {"name": "exchangeStatus", "type": "u8"}, {"name": "quoteDecimals", "type": "u8"}, {"name": "withdrawalMarginFactorBps", "type": "u16"}, {"name": "padding0", "type": {"array": ["u8", 4]}}, {"name": "padding1", "type": {"array": ["u64", 32]}}, {"name": "padding2", "type": {"array": ["u64", 32]}}, {"name": "padding3", "type": {"array": ["u64", 32]}}, {"name": "padding4", "type": {"array": ["u64", 32]}}, {"name": "padding5", "type": {"array": ["u64", 32]}}, {"name": "padding6", "type": {"array": ["u64", 32]}}, {"name": "padding7", "type": {"array": ["u64", 32]}}, {"name": "padding8", "type": {"array": ["u64", 32]}}]}}, {"name": "SequenceNumber", "docs": ["Pair of sequence number and last update slot."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "lastUpdateSlot", "type": "u64"}]}}, {"name": "PerpAssetMetadata", "docs": ["Opaque fixed-size zero-copy PerpAssetMetadata payload stored inside PerpAssetMap."], "type": {"kind": "struct", "fields": [{"name": "raw", "type": {"array": ["u8", 1568]}}]}}, {"name": "PerpAssetMapEntry", "docs": ["One fixed slot in the PerpAssetMap ShortMapV2 backing array."], "type": {"kind": "struct", "fields": [{"name": "key", "type": {"defined": {"name": "Symbol"}}}, {"name": "value", "type": {"defined": {"name": "PerpAssetMetadata"}}}]}}, {"name": "PerpAssetMetadataShortMapV2", "docs": ["Zero-copy ShortMapV2 layout."], "type": {"kind": "struct", "fields": [{"name": "slotsUsed", "type": "u32"}, {"name": "tombstones", "type": "u32"}, {"name": "capacity", "type": "u64"}, {"name": "data", "type": {"array": [{"defined": {"name": "PerpAssetMapEntry"}}, 1024]}}]}}, {"name": "PerpAssetMap", "docs": ["Canonical Eternal perpetual asset map account."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "numAssets", "type": "u16"}, {"name": "padding0", "type": {"array": ["u8", 6]}}, {"name": "metadatas", "type": {"defined": {"name": "PerpAssetMetadataShortMapV2"}}}]}}, {"name": "WithdrawThrottle", "docs": ["Fixed WithdrawThrottle payload embedded in WithdrawQueueHeader."], "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "remainingBudget", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "replenishAmountPerSlot", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "lastUpdateSlot", "type": "u64"}]}}, {"name": "WithdrawQueueHeader", "docs": ["Fixed WithdrawQueueHeader prefix for the Phoenix Eternal withdraw queue account.", "The deque of pending withdrawal requests follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "withdrawThrottle", "type": {"defined": {"name": "WithdrawThrottle"}}}, {"name": "totalQueuedAmount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "withdrawalFee", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "enqueueingFee", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "reserved", "type": {"array": ["u64", 5]}}]}}, {"name": "PermissionAccount", "docs": ["Fixed Phoenix Eternal permission delegation account layout."], "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "user", "type": "pubkey"}, {"name": "bump", "type": "u8"}, {"name": "padding1", "type": {"array": ["u8", 7]}}, {"name": "permission", "type": "u64"}, {"name": "expiresAtTimestamp", "type": "i64"}, {"name": "numSignerActionsRemaining", "type": "i64"}, {"name": "padding2", "type": {"array": ["u8", 64]}}]}}, {"name": "StopLoss", "docs": ["One fixed trigger order slot stored inside the StopLosses account."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "slot", "type": "u64"}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "flags", "type": "u8"}, {"name": "padding", "type": {"array": ["u8", 6]}}, {"name": "padding2", "type": {"array": ["u8", 32]}}]}}, {"name": "StopLosses", "docs": ["Fixed Phoenix Eternal StopLosses account layout."], "type": {"kind": "struct", "fields": [{"name": "stopLosses", "type": {"array": [{"defined": {"name": "StopLoss"}}, 2]}}, {"name": "initialized", "type": "u64"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "fundingKey", "type": "pubkey"}, {"name": "traderKey", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "assetIdPadding", "type": {"array": ["u8", 4]}}, {"name": "padding", "type": {"array": ["u64", 8]}}]}}, {"name": "EscrowHeader", "docs": ["Fixed EscrowHeader prefix for a Phoenix Eternal escrow account.", "The dynamic escrow request list follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "funderKey", "type": "pubkey"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "len", "type": "u64"}, {"name": "capacity", "type": "u64"}]}}, {"name": "ConditionalOrderHeader", "docs": ["Fixed ConditionalOrderHeader prefix for a Phoenix Eternal conditional orders account.", "The dynamic conditional order array follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "traderKey", "type": "pubkey"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "len", "type": "u8"}, {"name": "capacity", "type": "u8"}, {"name": "padding", "type": {"array": ["u8", 6]}}]}}, {"name": "RevokePermissionParams", "type": {"kind": "struct", "fields": [{"name": "permissionFlagsToRevoke", "type": "u64"}]}}, {"name": "SetPermissionInstruction", "type": {"kind": "struct", "fields": [{"name": "permission", "type": "u64"}, {"name": "expiresAtTimestamp", "type": {"option": "i64"}}, {"name": "allowedSignerActions", "type": {"option": "i64"}}]}}, {"name": "TraderCapabilityToggleTarget", "type": {"kind": "enum", "variants": [{"name": "PlaceLimitOrder"}, {"name": "PlaceMarketOrder"}, {"name": "RiskIncreasingTrade"}, {"name": "RiskReducingTrade"}, {"name": "DepositCollateral"}, {"name": "WithdrawCollateral"}]}}, {"name": "TraderCapabilityToggle", "type": {"kind": "struct", "fields": [{"name": "target", "type": {"defined": {"name": "TraderCapabilityToggleTarget"}}}, {"name": "enable", "type": "bool"}]}}, {"name": "TraderCapabilityUpdate", "type": {"kind": "struct", "fields": [{"name": "toggles", "type": {"vec": {"defined": {"name": "TraderCapabilityToggle"}}}}]}}, {"name": "TransferCollateralInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "TransferCollateralChildToParentInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "UncrossCrankParams", "type": {"kind": "struct", "fields": [{"name": "matchLimit", "type": "u64"}]}}, {"name": "ClearExpiredOrdersParams", "type": {"kind": "struct", "fields": [{"name": "maxOrdersPerSide", "type": {"option": "u64"}}]}}, {"name": "TriggerOrderParams", "docs": ["Trigger and execution parameters for one half of a conditional order."], "type": {"kind": "struct", "fields": [{"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}, {"name": "triggerPrice", "type": "u64"}, {"name": "executionPrice", "type": "u64"}]}}, {"name": "CreateConditionalOrdersAccountInstruction", "type": {"kind": "struct", "fields": [{"name": "capacity", "type": "u8"}]}}, {"name": "PlacePositionConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u32"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "sizeBaseLots", "type": {"option": "u64"}}, {"name": "sizePercent", "type": {"option": "u8"}}]}}, {"name": "CancelConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "index", "type": "u8"}, {"name": "disableFirst", "type": "bool"}, {"name": "disableSecond", "type": "bool"}]}}, {"name": "PlaceAttachedConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "orderId", "type": {"defined": {"name": "FIFOOrderId"}}}, {"name": "assetId", "type": "u32"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}]}}, {"name": "PlaceLimitOrderWithConditionalsInstruction", "type": {"kind": "struct", "fields": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}, {"name": "slot", "type": "u64"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}]}}, {"name": "ExecuteConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "index", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "OffChainMarketEvent", "docs": ["Borsh payload of the Log instruction after the 8-byte instruction discriminator."], "type": {"kind": "struct", "fields": [{"name": "batchIndex", "type": "u32"}, {"name": "events", "type": {"vec": {"defined": {"name": "MarketEvent"}}}}]}}, {"name": "OffChainMarketEventLengths", "docs": ["Borsh payload of the LogEventLengths instruction after the 8-byte instruction discriminator."], "type": {"kind": "struct", "fields": [{"name": "batchIndex", "type": "u32"}, {"name": "lengths", "type": {"vec": "u16"}}]}}, {"name": "SignedBaseLots", "docs": ["Signed quantity wrapper representing discrete base lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedQuoteLots", "docs": ["Signed quantity wrapper representing discrete quote lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedFeeRateMicro", "docs": ["Signed fee rate in millionths."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i32"}]}}, {"name": "SignedQuoteLotsPerBaseLot", "docs": ["Signed quote lots per base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedQuoteLotsPerBaseLotUpcasted", "docs": ["Upcasted signed quote lots per base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i128"}]}}, {"name": "SignedTicks", "docs": ["Signed discrete tick offset."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "FundingRateUnitInSeconds", "docs": ["Funding time unit measured in seconds."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "BaseLotsU32", "docs": ["u32-backed base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u32"}]}}, {"name": "BaseLotsPerTickU32", "docs": ["u32-backed spline liquidity density wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u32"}]}}, {"name": "BasisPoints", "docs": ["Basis points wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "Constant", "docs": ["Dimensionless constant wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OptionalNonZeroU64", "docs": ["Zero represents None; non-zero represents Some(value)."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OptionalFIFOOrderId", "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "TraderCapabilityFlags", "type": {"kind": "struct", "fields": [{"name": "bits", "type": "u32"}]}}, {"name": "MarketStatus", "type": {"kind": "enum", "variants": [{"name": "Uninitialized"}, {"name": "Active"}, {"name": "PostOnly"}, {"name": "Paused"}, {"name": "Closed"}, {"name": "Tombstoned"}]}}, {"name": "CommodityMarketState", "type": {"kind": "enum", "variants": [{"name": "Active"}, {"name": "AfterHours"}, {"name": "Reopen"}]}}, {"name": "AuthorityType", "type": {"kind": "enum", "variants": [{"name": "Root"}, {"name": "Risk"}, {"name": "Market"}, {"name": "Oracle"}, {"name": "Cancel"}, {"name": "Backstop"}, {"name": "ADL"}]}}, {"name": "ValidationRule", "type": {"kind": "enum", "variants": [{"name": "Ignore"}, {"name": "Require"}, {"name": "Forbid"}]}}, {"name": "LeverageTier", "type": {"kind": "struct", "fields": [{"name": "upperBoundSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "maxLeverage", "type": {"defined": {"name": "Constant"}}}, {"name": "limitOrderRiskFactor", "type": {"defined": {"name": "BasisPoints"}}}]}}, {"name": "LeverageTiers", "type": {"kind": "struct", "fields": [{"name": "tiers", "type": {"array": [{"defined": {"name": "LeverageTier"}}, 4]}}]}}, {"name": "PositionSizeLimits", "type": {"kind": "struct", "fields": [{"name": "long", "type": "u32"}, {"name": "short", "type": "u32"}]}}, {"name": "PositionSizeLimit", "type": {"kind": "enum", "variants": [{"name": "Disabled"}, {"name": "Limit", "fields": [{"name": "value", "type": {"defined": {"name": "PositionSizeLimits"}}}]}]}}, {"name": "TickRegion", "type": {"kind": "struct", "fields": [{"name": "startOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "endOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "density", "type": {"defined": {"name": "BaseLotsPerTickU32"}}}, {"name": "topLevelHiddenTakeSize", "type": {"defined": {"name": "BaseLotsPerTickU32"}}}, {"name": "totalSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLotsU32"}}}, {"name": "hiddenFilledSize", "type": {"defined": {"name": "BaseLotsU32"}}}, {"name": "lifespan", "type": "u64"}]}}, {"name": "OrderRejectionReason", "type": {"kind": "enum", "variants": [{"name": "TooManyLimitOrders"}, {"name": "PostOnlyCross"}, {"name": "InvalidOrderPacket"}, {"name": "TiFInvalid"}, {"name": "OutsideExecutionPriceBand"}]}}, {"name": "OrderResidualDiscardReason", "type": {"kind": "enum", "variants": [{"name": "OutsideExecutionPriceBand"}]}}, {"name": "OrderModificationReason", "type": {"kind": "enum", "variants": [{"name": "UserRequested"}, {"name": "AuthorityForced"}, {"name": "SelfTradeCancelProvide"}, {"name": "SelfTradeDecrementTake"}, {"name": "Expired"}, {"name": "ReduceOnlyInvalidated"}, {"name": "RiskCapacityExceeded"}, {"name": "BookCapacityEvicted"}, {"name": "Tombstone"}, {"name": "TopOfBookOutOfBounds"}, {"name": "StopLossCascade"}]}}, {"name": "ConditionalOrderPingStateSnapshot", "type": {"kind": "struct", "fields": [{"name": "value", "type": "u8"}]}}, {"name": "ConditionalOrderPingSnapshot", "type": {"kind": "struct", "fields": [{"name": "state", "type": {"defined": {"name": "ConditionalOrderPingStateSnapshot"}}}, {"name": "attachedOrderId", "type": {"defined": {"name": "OptionalFIFOOrderId"}}}, {"name": "maxSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "fillableSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "coPositionSequenceNumber", "type": {"option": "u8"}}]}}, {"name": "MarkPriceConfig", "type": {"kind": "struct", "fields": [{"name": "emaPeriodSlots", "type": "u64"}, {"name": "emaDiffRadius", "type": "u64"}, {"name": "bookPriceRadius", "type": "u64"}, {"name": "commoditiesAfterHoursRadius", "type": "u64"}, {"name": "spotPriceWeight", "type": "u64"}, {"name": "bookPriceWeight", "type": "u64"}, {"name": "perpPriceWeight", "type": "u64"}, {"name": "spotPriceStaleThreshold", "type": "u64"}, {"name": "bookPriceStaleThreshold", "type": "u64"}, {"name": "perpPriceStaleThreshold", "type": "u64"}, {"name": "riskActionPriceValidityRules", "type": {"array": [{"array": [{"array": [{"defined": {"name": "ValidationRule"}}, 8]}, 4]}, 8]}}, {"name": "oracleDivergenceRadius", "type": "u16"}, {"name": "minOracleResponses", "type": "u8"}, {"name": "commoditiesAfterHoursRadiusBps", "type": "u64"}]}}, {"name": "WithdrawConfig", "type": {"kind": "struct", "fields": [{"name": "depositCooldownPeriodInSlots", "type": "u64"}, {"name": "withdrawalFee", "type": "u64"}, {"name": "enqueueingFee", "type": "u64"}]}}, {"name": "WithdrawRateLimitConfig", "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": "u64"}, {"name": "replenishAmountPerSlot", "type": "u64"}]}}, {"name": "FundingConfig", "type": {"kind": "struct", "fields": [{"name": "fundingIntervalSeconds", "type": "u64"}, {"name": "fundingPeriodSeconds", "type": "u64"}, {"name": "maxFundingRate", "type": "i64"}]}}, {"name": "CommodityMarketStateConfig", "type": {"kind": "struct", "fields": [{"name": "marketState", "type": {"defined": {"name": "CommodityMarketState"}}}, {"name": "isCommodity", "type": "bool"}, {"name": "lastKnownIndexPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "lastIndexExpiryTimestamp", "type": "u64"}, {"name": "commoditiesAfterHoursRadius", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "MarketFeeConfig", "type": {"kind": "struct", "fields": [{"name": "defaultTakerFeeMicro", "type": "u32"}, {"name": "defaultMakerFeeMicro", "type": "i32"}]}}, {"name": "AdminParameterUpdateKind", "type": {"kind": "enum", "variants": [{"name": "CancelRiskFactor", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "IsolatedOnly", "kind": "struct", "fields": [{"name": "previous", "type": "bool"}, {"name": "new", "type": "bool"}]}, {"name": "LeverageTiers", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "LeverageTiers"}}}, {"name": "new", "type": {"defined": {"name": "LeverageTiers"}}}]}, {"name": "MarkPriceParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "MarkPriceConfig"}}}, {"name": "new", "type": {"defined": {"name": "MarkPriceConfig"}}}]}, {"name": "OpenInterestCap", "kind": "struct", "fields": [{"name": "previous", "type": "u64"}, {"name": "new", "type": "u64"}]}, {"name": "UpnlRiskFactor", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "UpnlRiskFactorForWithdrawals", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "WithdrawParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "WithdrawConfig"}}}, {"name": "new", "type": {"defined": {"name": "WithdrawConfig"}}}]}, {"name": "WithdrawRateLimits", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "WithdrawRateLimitConfig"}}}, {"name": "new", "type": {"defined": {"name": "WithdrawRateLimitConfig"}}}]}, {"name": "TraderCapability", "kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "previousFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}]}, {"name": "FundingParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "FundingConfig"}}}, {"name": "new", "type": {"defined": {"name": "FundingConfig"}}}]}, {"name": "MarketFees", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "MarketFeeConfig"}}}, {"name": "new", "type": {"defined": {"name": "MarketFeeConfig"}}}]}, {"name": "OpenInterestAdjustment", "kind": "struct", "fields": [{"name": "previousOpenInterest", "type": "u64"}, {"name": "newOpenInterest", "type": "u64"}]}, {"name": "CommodityMarketState", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "CommodityMarketStateConfig"}}}, {"name": "new", "type": {"defined": {"name": "CommodityMarketStateConfig"}}}]}]}}, {"name": "EscrowAction", "type": {"kind": "enum", "variants": [{"name": "Noop", "kind": "struct", "fields": [{"name": "padding0", "type": {"array": ["u8", 8]}}, {"name": "padding1", "type": {"array": ["u8", 128]}}]}, {"name": "Cash", "kind": "struct", "fields": [{"name": "amount", "type": "u64"}, {"name": "padding0", "type": {"array": ["u8", 128]}}]}]}}, {"name": "EscrowRequestCancelReason", "type": {"kind": "enum", "variants": [{"name": "Expiration"}, {"name": "CancelledBySender"}, {"name": "CancelledByReceiver"}]}}, {"name": "MarketEvent", "docs": ["Borsh enum emitted by Eternal Log instructions. Variant discriminators are one-byte ordinals."], "type": {"kind": "enum", "variants": [{"name": "SlotContext", "fields": [{"name": "value", "type": {"defined": {"name": "SlotContextEvent"}}}]}, {"name": "Header", "fields": [{"name": "value", "type": {"defined": {"name": "MarketEventHeader"}}}]}, {"name": "OrderPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "OrderPlacedEvent"}}}]}, {"name": "OrderFilled", "fields": [{"name": "value", "type": {"defined": {"name": "OrderFilledEvent"}}}]}, {"name": "OrderRejected", "fields": [{"name": "value", "type": {"defined": {"name": "OrderRejectedEvent"}}}]}, {"name": "SplineFilled", "fields": [{"name": "value", "type": {"defined": {"name": "SplineFilledEvent"}}}]}, {"name": "TradeSummary", "fields": [{"name": "value", "type": {"defined": {"name": "TradeSummaryEvent"}}}]}, {"name": "OrderModified", "fields": [{"name": "value", "type": {"defined": {"name": "OrderModifiedEvent"}}}]}, {"name": "MarketSummary", "fields": [{"name": "value", "type": {"defined": {"name": "MarketSummaryEvent"}}}]}, {"name": "TraderRegistered", "fields": [{"name": "value", "type": {"defined": {"name": "TraderRegisteredEvent"}}}]}, {"name": "TraderCollateralTransferred", "fields": [{"name": "value", "type": {"defined": {"name": "TraderCollateralTransferredEvent"}}}]}, {"name": "TraderActivated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderActivatedEvent"}}}]}, {"name": "TraderDeactivated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderDeactivatedEvent"}}}]}, {"name": "TraderFundsDeposited", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsDepositedEvent"}}}]}, {"name": "TraderFundsWithdrawn", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderFundsWithdrawnEnqueued", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderFundsWithdrawnDropped", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderWithdrawCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderWithdrawCancelledEvent"}}}]}, {"name": "TraderFundingSettled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundingSettledEvent"}}}]}, {"name": "SplineRegistered", "fields": [{"name": "value", "type": {"defined": {"name": "SplineRegisteredEvent"}}}]}, {"name": "SplineActivated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineActivatedEvent"}}}]}, {"name": "SplineDeactivated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineDeactivatedEvent"}}}]}, {"name": "SplinePriceUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePriceUpdatedEvent"}}}]}, {"name": "SplineParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineParametersUpdatedEvent"}}}]}, {"name": "MarketAdded", "fields": [{"name": "value", "type": {"defined": {"name": "MarketAddedEvent"}}}]}, {"name": "MarketStatusChanged", "fields": [{"name": "value", "type": {"defined": {"name": "MarketStatusChangedEvent"}}}]}, {"name": "MarketParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "MarketParametersUpdatedEvent"}}}]}, {"name": "FundingParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "FundingParametersUpdatedEvent"}}}]}, {"name": "FeesClaimed", "fields": [{"name": "value", "type": {"defined": {"name": "FeesClaimedEvent"}}}]}, {"name": "PricesUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "PricesUpdatedEvent"}}}]}, {"name": "LiquidationTransferSummary", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationTransferSummaryEvent"}}}]}, {"name": "LiquidationTransfer", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationTransferEvent"}}}]}, {"name": "Liquidation", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationEvent"}}}]}, {"name": "CloseMatchedPositions", "fields": [{"name": "value", "type": {"defined": {"name": "CloseMatchedPositionsEvent"}}}]}, {"name": "NameSuccessor", "fields": [{"name": "value", "type": {"defined": {"name": "NameSuccessorEvent"}}}]}, {"name": "ClaimAuthority", "fields": [{"name": "value", "type": {"defined": {"name": "ClaimAuthorityEvent"}}}]}, {"name": "WithdrawStateTransition", "fields": [{"name": "value", "type": {"defined": {"name": "WithdrawStateTransitionEvent"}}}]}, {"name": "PnL", "fields": [{"name": "value", "type": {"defined": {"name": "PnLEvent"}}}]}, {"name": "StopLossPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossPlacedEvent"}}}]}, {"name": "StopLossCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossCancelledEvent"}}}]}, {"name": "StopLossExecuted", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossExecutedEvent"}}}]}, {"name": "ExchangeStatusChanged", "fields": [{"name": "value", "type": {"defined": {"name": "ExchangeStatusChangedEvent"}}}]}, {"name": "TraderCapabilitiesEnabled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderCapabilitiesEnabledEvent"}}}]}, {"name": "TraderFundsWithdrawnFeePayment", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnFeePaymentEvent"}}}]}, {"name": "OrderPacket", "fields": [{"name": "value", "type": {"defined": {"name": "OrderPacketEvent"}}}]}, {"name": "SetPermission", "fields": [{"name": "value", "type": {"defined": {"name": "SetPermissionEvent"}}}]}, {"name": "AuthorityChanged", "fields": [{"name": "value", "type": {"defined": {"name": "AuthorityChangedEvent"}}}]}, {"name": "TraderDelegated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderDelegatedEvent"}}}]}, {"name": "AdminParameterUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "AdminParameterUpdatedEvent"}}}]}, {"name": "TraderFeesUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFeesUpdatedEvent"}}}]}, {"name": "MarketClosed", "fields": [{"name": "value", "type": {"defined": {"name": "MarketClosedEvent"}}}]}, {"name": "MarketDeleted", "fields": [{"name": "value", "type": {"defined": {"name": "MarketDeletedEvent"}}}]}, {"name": "EscrowAccountCreated", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowAccountCreatedEvent"}}}]}, {"name": "EscrowRequestCreated", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestCreatedEvent"}}}]}, {"name": "EscrowRequestAccepted", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestAcceptedEvent"}}}]}, {"name": "EscrowRequestCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestCancelledEvent"}}}]}, {"name": "SplinePriceUpdatedWithOrdering", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePriceUpdatedWithOrderingEvent"}}}]}, {"name": "SplineParametersUpdatedWithOrdering", "fields": [{"name": "value", "type": {"defined": {"name": "SplineParametersUpdatedWithOrderingEvent"}}}]}, {"name": "TriggerOrderPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderPlacedEvent"}}}]}, {"name": "TriggerOrderCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderCancelledEvent"}}}]}, {"name": "TriggerOrderExecuted", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderExecutedEvent"}}}]}, {"name": "PingInvalidated", "fields": [{"name": "value", "type": {"defined": {"name": "PingInvalidatedEvent"}}}]}, {"name": "PingActivated", "fields": [{"name": "value", "type": {"defined": {"name": "PingActivatedEvent"}}}]}, {"name": "SplinePositionLimitsConfigUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePositionLimitsConfigUpdatedEvent"}}}]}, {"name": "MarketTombstoned", "fields": [{"name": "value", "type": {"defined": {"name": "MarketTombstonedEvent"}}}]}, {"name": "ShutdownClosePositions", "fields": [{"name": "value", "type": {"defined": {"name": "ShutdownClosePositionsEvent"}}}]}, {"name": "OrderResidualDiscarded", "fields": [{"name": "value", "type": {"defined": {"name": "OrderResidualDiscardedEvent"}}}]}]}}, {"name": "SlotContextEvent", "type": {"kind": "struct", "fields": [{"name": "timestamp", "type": "u64"}, {"name": "slot", "type": "u64"}]}}, {"name": "MarketEventHeader", "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "tickSize", "type": "u32"}, {"name": "baseLotDecimals", "type": "i8"}, {"name": "quoteLotDecimals", "type": "u8"}, {"name": "signer", "type": "pubkey"}, {"name": "traderAccount", "type": "pubkey"}]}}, {"name": "OrderPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "orderId", "type": "u32"}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "orderSequenceNumber", "type": "u64"}, {"name": "prevOrderSequenceNumberSlot", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "quantity", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "lastValidSlot", "type": {"defined": {"name": "OptionalNonZeroU64"}}}, {"name": "initialSlot", "type": "u64"}]}}, {"name": "OrderPacketEvent", "type": {"kind": "struct", "fields": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}, {"name": "trader", "type": "pubkey"}, {"name": "nextOrderSequenceNumber", "type": "u64"}]}}, {"name": "OrderRejectedEvent", "type": {"kind": "struct", "fields": [{"name": "orderIndex", "type": "u32"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"array": ["u8", 32]}}]}}, {"name": "OrderResidualDiscardedEvent", "type": {"kind": "struct", "fields": [{"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "baseLotsDiscarded", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"defined": {"name": "OrderResidualDiscardReason"}}}]}}, {"name": "OrderFilledEvent", "type": {"kind": "struct", "fields": [{"name": "orderSequenceNumber", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "quantityRemaining", "type": {"defined": {"name": "BaseLots"}}}, {"name": "maker", "type": "pubkey"}, {"name": "makerFeeRate", "type": {"defined": {"name": "SignedFeeRateMicro"}}}, {"name": "makerBaseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "makerVirtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerQuoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerCumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "SplineFilledEvent", "type": {"kind": "struct", "fields": [{"name": "splineSequenceNumber", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "maker", "type": "pubkey"}, {"name": "makerFeeRate", "type": {"defined": {"name": "SignedFeeRateMicro"}}}, {"name": "makerBaseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "makerVirtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerQuoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerCumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "OrderModifiedEvent", "type": {"kind": "struct", "fields": [{"name": "orderSequenceNumber", "type": "u64"}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsReleased", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "quoteLotsReleased", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "baseLotsRemaining", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"defined": {"name": "OrderModificationReason"}}}]}}, {"name": "TradeSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "tradeSequenceNumber", "type": "u64"}, {"name": "prevTradeSequenceNumberSlot", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "feeInQuoteLots", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "baseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "quoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "cumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "MarketSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "openInterest", "type": {"defined": {"name": "BaseLots"}}}, {"name": "totalMakerQuoteLotFees", "type": {"option": {"defined": {"name": "SignedQuoteLots"}}}}, {"name": "totalTakerQuoteLotFees", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "markPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "spotPrice", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "TraderRegisteredEvent", "type": {"kind": "struct", "fields": [{"name": "traderSequenceNumber", "type": "u64"}, {"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "maxPositions", "type": "u64"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}]}}, {"name": "TraderDelegatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "oldPositionAuthority", "type": "pubkey"}, {"name": "newPositionAuthority", "type": "pubkey"}]}}, {"name": "TraderFeesUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "previousMakerFeeOverrideMultiplier", "type": "i8"}, {"name": "newMakerFeeOverrideMultiplier", "type": "i8"}, {"name": "previousTakerFeeOverrideMultiplier", "type": "i8"}, {"name": "newTakerFeeOverrideMultiplier", "type": "i8"}, {"name": "isHotTrader", "type": "bool"}]}}, {"name": "TraderCapabilitiesEnabledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "previousFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "globalTraderIndex", "type": "u32"}]}}, {"name": "TraderActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "globalTraderIndex", "type": "u32"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "TraderDeactivatedEvent", "type": {"kind": "struct", "fields": [{"name": "prevGlobalTraderIndex", "type": "u32"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "TraderFundsDepositedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "traderFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnFeePaymentEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "fee", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderWithdrawCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderCollateralTransferredEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "srcTrader", "type": "pubkey"}, {"name": "srcTraderSequenceNumber", "type": "u64"}, {"name": "srcTraderPrevSequenceNumberSlot", "type": "u64"}, {"name": "srcTraderNewCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "dstTrader", "type": "pubkey"}, {"name": "dstTraderSequenceNumber", "type": "u64"}, {"name": "dstTraderPrevSequenceNumberSlot", "type": "u64"}, {"name": "dstTraderNewCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "TraderFundingSettledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "fundingPayment", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "cumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}, {"name": "newCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "SplineRegisteredEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "SplineActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}]}}, {"name": "SplineDeactivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "SplinePositionLimitsConfigUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "maxPositionSize", "type": {"option": {"defined": {"name": "PositionSizeLimit"}}}}, {"name": "prevMaxPositionSize", "type": {"defined": {"name": "PositionSizeLimit"}}}, {"name": "leverageDecreaseInBps", "type": {"option": "u32"}}, {"name": "prevLeverageDecreaseInBps", "type": "u32"}]}}, {"name": "SplinePriceUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "priceInTicks", "type": "u64"}, {"name": "userUpdateSlot", "type": "u64"}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "SplineParametersUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}, {"name": "bidRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "askRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}]}}, {"name": "SplinePriceUpdatedWithOrderingEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "priceInTicks", "type": "u64"}, {"name": "userUpdateSlot", "type": "u64"}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}]}}, {"name": "SplineParametersUpdatedWithOrderingEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}, {"name": "bidRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "askRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}]}}, {"name": "StopLossPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "placeSlot", "type": "u64"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}]}}, {"name": "StopLossCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "StopLossExecutedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "TriggerOrderPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}, {"name": "maxSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "fillableSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "usePercent", "type": "bool"}, {"name": "percent", "type": "u8"}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "attachedOrderId", "type": {"defined": {"name": "OptionalFIFOOrderId"}}}]}}, {"name": "TriggerOrderCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "TriggerOrderExecutedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "PingInvalidatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "pre", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "post", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "currentPositionSequenceNumber", "type": "u8"}]}}, {"name": "PingActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "pre", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "post", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "bookOrderRemainingBaseLots", "type": {"option": {"defined": {"name": "BaseLots"}}}}]}}, {"name": "MarketAddedEvent", "type": {"kind": "struct", "fields": []}}, {"name": "MarketStatusChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "newMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}]}}, {"name": "MarketClosedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "finalizedMarkPrice", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "MarketTombstonedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "finalSequenceNumber", "type": "u64"}, {"name": "finalTradeSequenceNumber", "type": "u64"}, {"name": "finalOrderSequenceNumber", "type": "u64"}]}}, {"name": "ShutdownClosePositionsEvent", "type": {"kind": "struct", "fields": [{"name": "longTrader", "type": "pubkey"}, {"name": "shortTrader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "baseLotsClosed", "type": {"defined": {"name": "BaseLots"}}}, {"name": "settlementPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSequenceNumber", "type": "u64"}, {"name": "sequenceNumber", "type": "u64"}]}}, {"name": "MarketDeletedEvent", "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u32"}, {"name": "lamportsReclaimed", "type": "u64"}]}}, {"name": "ExchangeStatusChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousBits", "type": "u8"}, {"name": "newBits", "type": "u8"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "MarketParametersUpdatedEvent", "type": {"kind": "struct", "fields": []}}, {"name": "FundingParametersUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "newFundingIntervalSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "newFundingPeriodSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "newMaxFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}]}}, {"name": "FeesClaimedEvent", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "SetPermissionEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "user", "type": "pubkey"}, {"name": "previousPermission", "type": "u64"}, {"name": "newPermission", "type": "u64"}, {"name": "previousExpiresAtTimestamp", "type": "i64"}, {"name": "newExpiresAtTimestamp", "type": "i64"}, {"name": "previousNumSignerActionsRemaining", "type": "i64"}, {"name": "newNumSignerActionsRemaining", "type": "i64"}, {"name": "created", "type": "bool"}]}}, {"name": "PricesUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "oracleSigner", "type": {"option": "pubkey"}}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "newBestBid", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newBestAsk", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newLastTrade", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newExchangeSpotPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newExchangePerpPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newMidSpotDiffEmaTicks", "type": {"option": {"defined": {"name": "SignedTicks"}}}}, {"name": "newMarkPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "cumulativeFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}, {"name": "settledContribution", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}, {"name": "intervalAccumulator", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLotUpcasted"}}}}, {"name": "assetSequenceNumber", "type": "u64"}, {"name": "prevAssetSequenceNumberSlot", "type": "u64"}]}}, {"name": "LiquidationEvent", "type": {"kind": "struct", "fields": [{"name": "liquidator", "type": "pubkey"}, {"name": "liquidatedTrader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "liquidationSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "markPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "positionClosed", "type": "bool"}]}}, {"name": "LiquidationTransferSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "liquidatee", "type": "pubkey"}, {"name": "liquidator", "type": "pubkey"}, {"name": "totalTransfers", "type": "u32"}, {"name": "liquidateeCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "liquidatorCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "haircutCollected", "type": {"defined": {"name": "QuoteLots"}}}]}}, {"name": "LiquidationTransferEvent", "type": {"kind": "struct", "fields": [{"name": "liquidatee", "type": "pubkey"}, {"name": "liquidator", "type": "pubkey"}, {"name": "assetId", "type": "u64"}, {"name": "baseLotsTransferred", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotsTransferred", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "haircutRate", "type": "u16"}, {"name": "liquidateeCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "liquidatorCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "CloseMatchedPositionsEvent", "type": {"kind": "struct", "fields": [{"name": "caller", "type": "pubkey"}, {"name": "closedShort", "type": "pubkey"}, {"name": "closedLong", "type": "pubkey"}, {"name": "inProfitAccount", "type": "pubkey"}, {"name": "assetId", "type": "u64"}, {"name": "baseLotsClosed", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "atLossCloseValue", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "inProfitCloseValue", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "atLossCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "inProfitCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "NameSuccessorEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}]}}, {"name": "ClaimAuthorityEvent", "type": {"kind": "struct", "fields": [{"name": "previousAuthority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}]}}, {"name": "AuthorityChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousAuthority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}, {"name": "authorityType", "type": {"defined": {"name": "AuthorityType"}}}]}}, {"name": "WithdrawStateTransitionEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "fromState", "type": "u8"}, {"name": "toState", "type": "u8"}, {"name": "reason", "type": "u8"}, {"name": "transitionCount", "type": "u16"}, {"name": "nodeIndex", "type": {"defined": {"name": "NodePointer"}}}]}}, {"name": "PnLEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "realizedPnl", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "fundingPayment", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "baseLotsBefore", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "baseLotsAfter", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotsBefore", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "virtualQuoteLotsAfter", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "AdminParameterUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "assetSymbol", "type": {"option": {"defined": {"name": "Symbol"}}}}, {"name": "assetId", "type": {"option": "u32"}}, {"name": "updateKind", "type": {"defined": {"name": "AdminParameterUpdateKind"}}}]}}, {"name": "EscrowAccountCreatedEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "capacity", "type": "u64"}]}}, {"name": "EscrowRequestCreatedEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "senderAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "senderPdaIndex", "type": "u8"}, {"name": "senderSubaccountIndex", "type": "u8"}, {"name": "receiverPdaIndex", "type": "u8"}, {"name": "receiverSubaccountIndex", "type": "u8"}, {"name": "expirationOffset", "type": "u32"}, {"name": "initialSlot", "type": "u64"}, {"name": "actions", "type": {"array": [{"defined": {"name": "EscrowAction"}}, 4]}}]}}, {"name": "EscrowRequestAcceptedEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}]}}, {"name": "EscrowRequestCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "reason", "type": {"defined": {"name": "EscrowRequestCancelReason"}}}]}}, {"name": "ChangeMarketStatusParams", "docs": ["Borsh payload for changing an orderbook market status."], "type": {"kind": "struct", "fields": [{"name": "nextMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}]}}, {"name": "AuthorizedForceCancelParams", "docs": ["Borsh payload for authority-driven force cancellation by remaining side size."], "type": {"kind": "struct", "fields": [{"name": "remainingBidBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "remainingAskBaseLots", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "AuthorizedForceCancelByIdParams", "docs": ["Borsh payload for authority-driven force cancellation by order id."], "type": {"kind": "struct", "fields": [{"name": "bidOrderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}, {"name": "askOrderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}]}}, {"name": "CreateEscrowAccountParams", "docs": ["Borsh payload for creating an escrow account."], "type": {"kind": "struct", "fields": [{"name": "capacity", "type": "u64"}]}}, {"name": "EscrowParticipantMetadata", "docs": ["Trader PDA/subaccount metadata stored with escrow requests."], "type": {"kind": "struct", "fields": [{"name": "senderPdaIndex", "type": "u8"}, {"name": "senderSubaccountIndex", "type": "u8"}, {"name": "receiverPdaIndex", "type": "u8"}, {"name": "receiverSubaccountIndex", "type": "u8"}]}}, {"name": "CreateEscrowRequestParams", "docs": ["Borsh payload for creating an escrow request."], "type": {"kind": "struct", "fields": [{"name": "participantMetadata", "type": {"defined": {"name": "EscrowParticipantMetadata"}}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "actions", "type": {"vec": {"defined": {"name": "EscrowAction"}}}}]}}, {"name": "CancelEscrowRequestParams", "docs": ["Borsh payload for cancelling an escrow request by sequence number."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}]}}, {"name": "UpsertEscrowRequestParams", "docs": ["Borsh payload for upserting an escrow request."], "type": {"kind": "struct", "fields": [{"name": "participantMetadata", "type": {"defined": {"name": "EscrowParticipantMetadata"}}}, {"name": "action", "type": {"defined": {"name": "EscrowAction"}}}]}}, {"name": "RegisterSplineParams", "docs": ["Empty Borsh payload for registering a spline trader."], "type": {"kind": "struct", "fields": []}}, {"name": "UpdateCommodityMarketStateInstruction", "docs": ["Borsh payload for updating commodity market state and mark price inputs."], "type": {"kind": "struct", "fields": [{"name": "marketState", "type": {"defined": {"name": "CommodityMarketState"}}}, {"name": "lastKnownIndexPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "lastIndexExpiryTimestamp", "type": "u64"}, {"name": "emaPeriodSlots", "type": {"option": "u64"}}]}}, {"name": "UpdatePerpIsolatedOnlyInstruction", "docs": ["Borsh payload for updating isolated-only metadata for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "isolatedOnly", "type": "u8"}]}}, {"name": "UpdatePerpLeverageTiersInstruction", "docs": ["Borsh payload for updating leverage tiers for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "leverageTiers", "type": {"defined": {"name": "LeverageTiers"}}}]}}, {"name": "UpdatePerpOpenInterestCapInstruction", "docs": ["Borsh payload for updating open interest cap for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "openInterestCap", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "UPnlRiskFactor", "docs": ["uPnL risk factor encoded as basis points."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "UpdatePerpUPnlRiskFactorInstruction", "docs": ["Borsh payload for updating uPnL risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "upnlRiskFactor", "type": {"defined": {"name": "UPnlRiskFactor"}}}]}}, {"name": "UpdatePerpUPnlRiskFactorForWithdrawalsInstruction", "docs": ["Borsh payload for updating withdrawal uPnL risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "upnlRiskFactorForWithdrawals", "type": {"defined": {"name": "UPnlRiskFactor"}}}]}}, {"name": "UpdatePerpCancelRiskFactorInstruction", "docs": ["Borsh payload for updating cancel-order risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "cancelOrderRiskFactor", "type": "u16"}]}}, {"name": "UpdatePerpMarkPriceParametersInstruction", "docs": ["Borsh payload for updating mark price calculation parameters for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "emaPeriodSlots", "type": {"option": "u64"}}, {"name": "emaDiffRadius", "type": {"option": "u64"}}, {"name": "bookPriceRadius", "type": {"option": "u64"}}, {"name": "commoditiesAfterHoursRadiusBps", "type": {"option": "u64"}}, {"name": "adjustedExchangeSpotPriceWeight", "type": {"option": "u64"}}, {"name": "bookPriceWeight", "type": {"option": "u64"}}, {"name": "exchangePerpPriceWeight", "type": {"option": "u64"}}, {"name": "spotPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "bookPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "perpPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "riskActionPriceValidityRules", "type": {"option": {"array": [{"array": [{"array": [{"defined": {"name": "ValidationRule"}}, 8]}, 4]}, 8]}}}, {"name": "oracleDivergenceRadius", "type": {"option": "u16"}}, {"name": "minOracleResponses", "type": {"option": "u8"}}]}}, {"name": "UpdateTraderFeesParams", "docs": ["Borsh payload for updating trader fee override multipliers."], "type": {"kind": "struct", "fields": [{"name": "makerFeeOverrideMultiplier", "type": "i8"}, {"name": "takerFeeOverrideMultiplier", "type": "i8"}]}}, {"name": "UpdateMarketFeesParams", "docs": ["Borsh payload for updating market default fees."], "type": {"kind": "struct", "fields": [{"name": "defaultTakerFeeMicro", "type": {"option": "u32"}}, {"name": "defaultMakerFeeMicro", "type": {"option": "i32"}}]}}, {"name": "UpdateWithdrawRateLimitsParams", "docs": ["Borsh payload for updating withdraw throttle rate limits."], "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": {"option": "u64"}}, {"name": "replenishAmountPerSlot", "type": {"option": "u64"}}]}}, {"name": "UpdateWithdrawParametersParams", "docs": ["Borsh payload for updating withdraw cooldown and fee parameters."], "type": {"kind": "struct", "fields": [{"name": "depositCooldownPeriodInSlots", "type": {"option": "u64"}}, {"name": "withdrawalFee", "type": {"option": "u64"}}, {"name": "enqueueingFee", "type": {"option": "u64"}}]}}, {"name": "UpdateAuthoritiesParams", "docs": ["Borsh payload for root authority updates to subordinate authorities."], "type": {"kind": "struct", "fields": [{"name": "newRiskAuthority", "type": {"option": "pubkey"}}, {"name": "newMarketAuthority", "type": {"option": "pubkey"}}, {"name": "newOracleAuthority", "type": {"option": "pubkey"}}, {"name": "newCancelAuthority", "type": {"option": "pubkey"}}, {"name": "newBackstopAuthority", "type": {"option": "pubkey"}}, {"name": "newAdlAuthority", "type": {"option": "pubkey"}}]}}, {"name": "ArenaType", "docs": ["Arena account family to initialize."], "type": {"kind": "enum", "variants": [{"name": "GlobalTraderIndex"}, {"name": "ActiveTraderBuffer"}]}}, {"name": "InitializeArenaParams", "docs": ["Borsh payload for initializing a new arena account."], "type": {"kind": "struct", "fields": [{"name": "arenaType", "type": {"defined": {"name": "ArenaType"}}}]}}, {"name": "RemoveOracleInstruction", "docs": ["Borsh payload for removing oracle public keys from the perp asset map."], "type": {"kind": "struct", "fields": [{"name": "oracleKeys", "type": {"vec": "pubkey"}}]}}, {"name": "RemoveAllOraclesInstruction", "docs": ["Borsh payload for removing all oracle keys for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "UpdateFundingParametersInstruction", "docs": ["Borsh payload for updating funding parameters for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "fundingIntervalSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "fundingPeriodSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "maxFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}]}}, {"name": "UpdateSplinePositionLimitsConfigParams", "docs": ["Borsh payload for updating spline position-size limits and leverage decrease configuration."], "type": {"kind": "struct", "fields": [{"name": "maxPositionSize", "type": {"option": {"defined": {"name": "PositionSizeLimit"}}}}, {"name": "leverageDecreaseInBps", "type": {"option": "u32"}}]}}, {"name": "TraderFundsWithdrawnEnqueuedEvent", "docs": ["Viewer-safe alias for MarketEvent::TraderFundsWithdrawnEnqueued.", "Uses the same payload layout as TraderFundsWithdrawnEvent."], "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnDroppedEvent", "docs": ["Viewer-safe alias for MarketEvent::TraderFundsWithdrawnDropped.", "Uses the same payload layout as TraderFundsWithdrawnEvent."], "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "DirectMarkOverride", "type": {"kind": "struct", "fields": [{"name": "symbol", "type": "string"}, {"name": "target_ticks", "type": "string"}]}}, {"name": "ReferencePriceOverride", "type": {"kind": "struct", "fields": [{"name": "symbol", "type": "string"}, {"name": "spot_ticks", "type": "string"}, {"name": "perp_ticks", "type": "string"}]}}], "events": [{"name": "SlotContextEvent", "docs": ["MarketEvent::SlotContext Borsh variant 0.", "Payload type: SlotContextEvent."], "discriminator": [0]}, {"name": "MarketEventHeader", "docs": ["MarketEvent::Header Borsh variant 1.", "Payload type: MarketEventHeader."], "discriminator": [1]}, {"name": "OrderPlacedEvent", "docs": ["MarketEvent::OrderPlaced Borsh variant 2.", "Payload type: OrderPlacedEvent."], "discriminator": [2]}, {"name": "OrderFilledEvent", "docs": ["MarketEvent::OrderFilled Borsh variant 3.", "Payload type: OrderFilledEvent."], "discriminator": [3]}, {"name": "OrderRejectedEvent", "docs": ["MarketEvent::OrderRejected Borsh variant 4.", "Payload type: OrderRejectedEvent."], "discriminator": [4]}, {"name": "SplineFilledEvent", "docs": ["MarketEvent::SplineFilled Borsh variant 5.", "Payload type: SplineFilledEvent."], "discriminator": [5]}, {"name": "TradeSummaryEvent", "docs": ["MarketEvent::TradeSummary Borsh variant 6.", "Payload type: TradeSummaryEvent."], "discriminator": [6]}, {"name": "OrderModifiedEvent", "docs": ["MarketEvent::OrderModified Borsh variant 7.", "Payload type: OrderModifiedEvent."], "discriminator": [7]}, {"name": "MarketSummaryEvent", "docs": ["MarketEvent::MarketSummary Borsh variant 8.", "Payload type: MarketSummaryEvent."], "discriminator": [8]}, {"name": "TraderRegisteredEvent", "docs": ["MarketEvent::TraderRegistered Borsh variant 9.", "Payload type: TraderRegisteredEvent."], "discriminator": [9]}, {"name": "TraderCollateralTransferredEvent", "docs": ["MarketEvent::TraderCollateralTransferred Borsh variant 10.", "Payload type: TraderCollateralTransferredEvent."], "discriminator": [10]}, {"name": "TraderActivatedEvent", "docs": ["MarketEvent::TraderActivated Borsh variant 11.", "Payload type: TraderActivatedEvent."], "discriminator": [11]}, {"name": "TraderDeactivatedEvent", "docs": ["MarketEvent::TraderDeactivated Borsh variant 12.", "Payload type: TraderDeactivatedEvent."], "discriminator": [12]}, {"name": "TraderFundsDepositedEvent", "docs": ["MarketEvent::TraderFundsDeposited Borsh variant 13.", "Payload type: TraderFundsDepositedEvent."], "discriminator": [13]}, {"name": "TraderFundsWithdrawnEvent", "docs": ["MarketEvent::TraderFundsWithdrawn Borsh variant 14.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [14]}, {"name": "TraderFundsWithdrawnEnqueuedEvent", "docs": ["MarketEvent::TraderFundsWithdrawnEnqueued Borsh variant 15.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [15]}, {"name": "TraderFundsWithdrawnDroppedEvent", "docs": ["MarketEvent::TraderFundsWithdrawnDropped Borsh variant 16.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [16]}, {"name": "TraderWithdrawCancelledEvent", "docs": ["MarketEvent::TraderWithdrawCancelled Borsh variant 17.", "Payload type: TraderWithdrawCancelledEvent."], "discriminator": [17]}, {"name": "TraderFundingSettledEvent", "docs": ["MarketEvent::TraderFundingSettled Borsh variant 18.", "Payload type: TraderFundingSettledEvent."], "discriminator": [18]}, {"name": "SplineRegisteredEvent", "docs": ["MarketEvent::SplineRegistered Borsh variant 19.", "Payload type: SplineRegisteredEvent."], "discriminator": [19]}, {"name": "SplineActivatedEvent", "docs": ["MarketEvent::SplineActivated Borsh variant 20.", "Payload type: SplineActivatedEvent."], "discriminator": [20]}, {"name": "SplineDeactivatedEvent", "docs": ["MarketEvent::SplineDeactivated Borsh variant 21.", "Payload type: SplineDeactivatedEvent."], "discriminator": [21]}, {"name": "SplinePriceUpdatedEvent", "docs": ["MarketEvent::SplinePriceUpdated Borsh variant 22.", "Payload type: SplinePriceUpdatedEvent."], "discriminator": [22]}, {"name": "SplineParametersUpdatedEvent", "docs": ["MarketEvent::SplineParametersUpdated Borsh variant 23.", "Payload type: SplineParametersUpdatedEvent."], "discriminator": [23]}, {"name": "MarketAddedEvent", "docs": ["MarketEvent::MarketAdded Borsh variant 24.", "Payload type: MarketAddedEvent."], "discriminator": [24]}, {"name": "MarketStatusChangedEvent", "docs": ["MarketEvent::MarketStatusChanged Borsh variant 25.", "Payload type: MarketStatusChangedEvent."], "discriminator": [25]}, {"name": "MarketParametersUpdatedEvent", "docs": ["MarketEvent::MarketParametersUpdated Borsh variant 26.", "Payload type: MarketParametersUpdatedEvent."], "discriminator": [26]}, {"name": "FundingParametersUpdatedEvent", "docs": ["MarketEvent::FundingParametersUpdated Borsh variant 27.", "Payload type: FundingParametersUpdatedEvent."], "discriminator": [27]}, {"name": "FeesClaimedEvent", "docs": ["MarketEvent::FeesClaimed Borsh variant 28.", "Payload type: FeesClaimedEvent."], "discriminator": [28]}, {"name": "PricesUpdatedEvent", "docs": ["MarketEvent::PricesUpdated Borsh variant 29.", "Payload type: PricesUpdatedEvent."], "discriminator": [29]}, {"name": "LiquidationTransferSummaryEvent", "docs": ["MarketEvent::LiquidationTransferSummary Borsh variant 30.", "Payload type: LiquidationTransferSummaryEvent."], "discriminator": [30]}, {"name": "LiquidationTransferEvent", "docs": ["MarketEvent::LiquidationTransfer Borsh variant 31.", "Payload type: LiquidationTransferEvent."], "discriminator": [31]}, {"name": "LiquidationEvent", "docs": ["MarketEvent::Liquidation Borsh variant 32.", "Payload type: LiquidationEvent."], "discriminator": [32]}, {"name": "CloseMatchedPositionsEvent", "docs": ["MarketEvent::CloseMatchedPositions Borsh variant 33.", "Payload type: CloseMatchedPositionsEvent."], "discriminator": [33]}, {"name": "NameSuccessorEvent", "docs": ["MarketEvent::NameSuccessor Borsh variant 34.", "Payload type: NameSuccessorEvent."], "discriminator": [34]}, {"name": "ClaimAuthorityEvent", "docs": ["MarketEvent::ClaimAuthority Borsh variant 35.", "Payload type: ClaimAuthorityEvent."], "discriminator": [35]}, {"name": "WithdrawStateTransitionEvent", "docs": ["MarketEvent::WithdrawStateTransition Borsh variant 36.", "Payload type: WithdrawStateTransitionEvent."], "discriminator": [36]}, {"name": "PnLEvent", "docs": ["MarketEvent::PnL Borsh variant 37.", "Payload type: PnLEvent."], "discriminator": [37]}, {"name": "StopLossPlacedEvent", "docs": ["MarketEvent::StopLossPlaced Borsh variant 38.", "Payload type: StopLossPlacedEvent."], "discriminator": [38]}, {"name": "StopLossCancelledEvent", "docs": ["MarketEvent::StopLossCancelled Borsh variant 39.", "Payload type: StopLossCancelledEvent."], "discriminator": [39]}, {"name": "StopLossExecutedEvent", "docs": ["MarketEvent::StopLossExecuted Borsh variant 40.", "Payload type: StopLossExecutedEvent."], "discriminator": [40]}, {"name": "ExchangeStatusChangedEvent", "docs": ["MarketEvent::ExchangeStatusChanged Borsh variant 41.", "Payload type: ExchangeStatusChangedEvent."], "discriminator": [41]}, {"name": "TraderCapabilitiesEnabledEvent", "docs": ["MarketEvent::TraderCapabilitiesEnabled Borsh variant 42.", "Payload type: TraderCapabilitiesEnabledEvent."], "discriminator": [42]}, {"name": "TraderFundsWithdrawnFeePaymentEvent", "docs": ["MarketEvent::TraderFundsWithdrawnFeePayment Borsh variant 43.", "Payload type: TraderFundsWithdrawnFeePaymentEvent."], "discriminator": [43]}, {"name": "OrderPacketEvent", "docs": ["MarketEvent::OrderPacket Borsh variant 44.", "Payload type: OrderPacketEvent."], "discriminator": [44]}, {"name": "SetPermissionEvent", "docs": ["MarketEvent::SetPermission Borsh variant 45.", "Payload type: SetPermissionEvent."], "discriminator": [45]}, {"name": "AuthorityChangedEvent", "docs": ["MarketEvent::AuthorityChanged Borsh variant 46.", "Payload type: AuthorityChangedEvent."], "discriminator": [46]}, {"name": "TraderDelegatedEvent", "docs": ["MarketEvent::TraderDelegated Borsh variant 47.", "Payload type: TraderDelegatedEvent."], "discriminator": [47]}, {"name": "AdminParameterUpdatedEvent", "docs": ["MarketEvent::AdminParameterUpdated Borsh variant 48.", "Payload type: AdminParameterUpdatedEvent."], "discriminator": [48]}, {"name": "TraderFeesUpdatedEvent", "docs": ["MarketEvent::TraderFeesUpdated Borsh variant 49.", "Payload type: TraderFeesUpdatedEvent."], "discriminator": [49]}, {"name": "MarketClosedEvent", "docs": ["MarketEvent::MarketClosed Borsh variant 50.", "Payload type: MarketClosedEvent."], "discriminator": [50]}, {"name": "MarketDeletedEvent", "docs": ["MarketEvent::MarketDeleted Borsh variant 51.", "Payload type: MarketDeletedEvent."], "discriminator": [51]}, {"name": "EscrowAccountCreatedEvent", "docs": ["MarketEvent::EscrowAccountCreated Borsh variant 52.", "Payload type: EscrowAccountCreatedEvent."], "discriminator": [52]}, {"name": "EscrowRequestCreatedEvent", "docs": ["MarketEvent::EscrowRequestCreated Borsh variant 53.", "Payload type: EscrowRequestCreatedEvent."], "discriminator": [53]}, {"name": "EscrowRequestAcceptedEvent", "docs": ["MarketEvent::EscrowRequestAccepted Borsh variant 54.", "Payload type: EscrowRequestAcceptedEvent."], "discriminator": [54]}, {"name": "EscrowRequestCancelledEvent", "docs": ["MarketEvent::EscrowRequestCancelled Borsh variant 55.", "Payload type: EscrowRequestCancelledEvent."], "discriminator": [55]}, {"name": "SplinePriceUpdatedWithOrderingEvent", "docs": ["MarketEvent::SplinePriceUpdatedWithOrdering Borsh variant 56.", "Payload type: SplinePriceUpdatedWithOrderingEvent."], "discriminator": [56]}, {"name": "SplineParametersUpdatedWithOrderingEvent", "docs": ["MarketEvent::SplineParametersUpdatedWithOrdering Borsh variant 57.", "Payload type: SplineParametersUpdatedWithOrderingEvent."], "discriminator": [57]}, {"name": "TriggerOrderPlacedEvent", "docs": ["MarketEvent::TriggerOrderPlaced Borsh variant 58.", "Payload type: TriggerOrderPlacedEvent."], "discriminator": [58]}, {"name": "TriggerOrderCancelledEvent", "docs": ["MarketEvent::TriggerOrderCancelled Borsh variant 59.", "Payload type: TriggerOrderCancelledEvent."], "discriminator": [59]}, {"name": "TriggerOrderExecutedEvent", "docs": ["MarketEvent::TriggerOrderExecuted Borsh variant 60.", "Payload type: TriggerOrderExecutedEvent."], "discriminator": [60]}, {"name": "PingInvalidatedEvent", "docs": ["MarketEvent::PingInvalidated Borsh variant 61.", "Payload type: PingInvalidatedEvent."], "discriminator": [61]}, {"name": "PingActivatedEvent", "docs": ["MarketEvent::PingActivated Borsh variant 62.", "Payload type: PingActivatedEvent."], "discriminator": [62]}, {"name": "SplinePositionLimitsConfigUpdatedEvent", "docs": ["MarketEvent::SplinePositionLimitsConfigUpdated Borsh variant 63.", "Payload type: SplinePositionLimitsConfigUpdatedEvent."], "discriminator": [63]}, {"name": "MarketTombstonedEvent", "docs": ["MarketEvent::MarketTombstoned Borsh variant 64.", "Payload type: MarketTombstonedEvent."], "discriminator": [64]}, {"name": "ShutdownClosePositionsEvent", "docs": ["MarketEvent::ShutdownClosePositions Borsh variant 65.", "Payload type: ShutdownClosePositionsEvent."], "discriminator": [65]}, {"name": "OrderResidualDiscardedEvent", "docs": ["MarketEvent::OrderResidualDiscarded Borsh variant 66.", "Payload type: OrderResidualDiscardedEvent."], "discriminator": [66]}], "errors": []} \ No newline at end of file diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/mod.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/mod.rs index d7e8cec45..75dcf65a6 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/mod.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/mod.rs @@ -1 +1,2 @@ +pub mod collateral; pub mod state_builder; diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml index 022181bfa..7189c4651 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml @@ -1,263 +1,28 @@ protocol: Phoenix Eternal version: v1 -account_type: TraderCollateralOverride -idl_file_path: editor-schema.json +account_type: Trader +idl_file_path: idl.json tags: - perpetuals - collateral - risk -constants: - # The perp markets listed on the live PerpAssetMap (captured 2026-08-31). A newly - # listed market reaches this catalog on the next update; until then the raw - # scenario API accepts any symbol and the writer validates it against live state. - market_symbol: - label: Market - description: Select a Phoenix Eternal perp market - options: - - id: aapl - label: AAPL - value: AAPL - - id: aave - label: AAVE - value: AAVE - - id: ada - label: ADA - value: ADA - - id: amat - label: AMAT - value: AMAT - - id: amd - label: AMD - value: AMD - - id: amzn - label: AMZN - value: AMZN - - id: ansem - label: ANSEM - value: ANSEM - - id: arm - label: ARM - value: ARM - - id: asml - label: ASML - value: ASML - - id: avgo - label: AVGO - value: AVGO - - id: baba - label: BABA - value: BABA - - id: bnb - label: BNB - value: BNB - - id: btc - label: BTC - value: BTC - - id: cbrs - label: CBRS - value: CBRS - - id: chip - label: CHIP - value: CHIP - - id: coin - label: COIN - value: COIN - - id: copper - label: COPPER - value: COPPER - - id: crcl - label: CRCL - value: CRCL - - id: crv - label: CRV - value: CRV - - id: crwd - label: CRWD - value: CRWD - - id: crwv - label: CRWV - value: CRWV - - id: doge - label: DOGE - value: DOGE - - id: ena - label: ENA - value: ENA - - id: eth - label: ETH - value: ETH - - id: fartcoin - label: FARTCOIN - value: FARTCOIN - - id: fet - label: FET - value: FET - - id: gold - label: GOLD - value: GOLD - - id: googl - label: GOOGL - value: GOOGL - - id: hood - label: HOOD - value: HOOD - - id: hype - label: HYPE - value: HYPE - - id: intc - label: INTC - value: INTC - - id: iren - label: IREN - value: IREN - - id: jto - label: JTO - value: JTO - - id: jup - label: JUP - value: JUP - - id: link - label: LINK - value: LINK - - id: lit - label: LIT - value: LIT - - id: lly - label: LLY - value: LLY - - id: mega - label: MEGA - value: MEGA - - id: met - label: MET - value: MET - - id: meta - label: META - value: META - - id: mon - label: MON - value: MON - - id: morpho - label: MORPHO - value: MORPHO - - id: mrna - label: MRNA - value: MRNA - - id: mrvl - label: MRVL - value: MRVL - - id: msft - label: MSFT - value: MSFT - - id: mstr - label: MSTR - value: MSTR - - id: mu - label: MU - value: MU - - id: nbis - label: NBIS - value: NBIS - - id: near - label: NEAR - value: NEAR - - id: net - label: NET - value: NET - - id: nflx - label: NFLX - value: NFLX - - id: nvda - label: NVDA - value: NVDA - - id: ondo - label: ONDO - value: ONDO - - id: pltr - label: PLTR - value: PLTR - - id: pump - label: PUMP - value: PUMP - - id: qcom - label: QCOM - value: QCOM - - id: render - label: RENDER - value: RENDER - - id: silver - label: SILVER - value: SILVER - - id: skhy - label: SKHY - value: SKHY - - id: skr - label: SKR - value: SKR - - id: sndk - label: SNDK - value: SNDK - - id: sol - label: SOL - value: SOL - - id: spcx - label: SPCX - value: SPCX - - id: sui - label: SUI - value: SUI - - id: tao - label: TAO - value: TAO - - id: trx - label: TRX - value: TRX - - id: tsla - label: TSLA - value: TSLA - - id: tsm - label: TSM - value: TSM - - id: virtual - label: VIRTUAL - value: VIRTUAL - - id: vvv - label: VVV - value: VVV - - id: wld - label: WLD - value: WLD - - id: wtioil - label: WTIOIL - value: WTIOIL - - id: xlm - label: XLM - value: XLM - - id: xpl - label: XPL - value: XPL - - id: xrp - label: XRP - value: XRP - - id: zec - label: ZEC - value: ZEC - templates: - id: phoenix-trader-collateral-stress name: Phoenix Trader Collateral Stress - description: Set exact signed quote-lot collateral on one validated Phoenix Eternal Trader account. - idl_account_name: TraderCollateralOverride + description: Set exact signed quote-lot collateral on a Phoenix Trader and its effective GlobalTraderIndex entry when hot. + idl_account_name: Trader properties: - - path: quote_lot_collateral + - path: traderState.quoteLotCollateral label: Target Collateral in Quote Lots description: Exact signed quote-lot collateral encoded as a decimal string llm_context: | This prepares collateral stress state. It does not guarantee liquidation and does not execute liquidation. + Hot traders read collateral from GlobalTraderIndex. Play resolves the reachable entry by trader key and updates both records through the IDL, preserving unrelated bytes. Cold traders use their own account. + Hot traders currently require a valid single-arena GlobalTraderIndex; unsupported layouts are rejected before collateral writes. For a liquidation cascade, pair it with phoenix-direct-mark-risk-shock at the next slot. Keep override labels short. - Pass quote_lot_collateral as a decimal string. Builder-created scenarios leave fetchBeforeUse off; the value applies to the local fork state. + Pass traderState.quoteLotCollateral as a decimal string. Builder-created scenarios leave fetchBeforeUse off; the value applies to the local fork state. address: type: pubkey @@ -267,9 +32,9 @@ templates: idl_account_name: DirectMarkOverride properties: - path: symbol - type: constant_ref + type: dynamic_ref label: Market Symbol - constant: market_symbol + source: list_phoenix_markets description: Exact Phoenix market symbol - path: target_ticks label: Target Mark Ticks @@ -291,9 +56,9 @@ templates: idl_account_name: ReferencePriceOverride properties: - path: symbol - type: constant_ref + type: dynamic_ref label: Market Symbol - constant: market_symbol + source: list_phoenix_markets description: Exact Phoenix market symbol - path: spot_ticks label: Spot Reference Ticks diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs index 8a65dece4..2e2990d00 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs @@ -2,15 +2,14 @@ use core::mem::size_of; use std::collections::HashMap; use phoenix_rise_accounts::{ - PhoenixAccountDecodeError, + PhoenixAccount, PhoenixAccountDecodeError, global_config::GlobalConfig, perp_asset_map::{ MarkPrice, PerpAssetMap, PerpPriceComponent, PriceComponent, SpotPriceComponent, TicksAtSlot, }, - trader::{Trader, TraderHeader}, }; -use phoenix_rise_math::quantities::{SignedQuoteLots, Ticks}; +use phoenix_rise_math::quantities::Ticks; use solana_account::Account; use solana_pubkey::Pubkey; use surfpool_types::{AccountAddress, OverrideInstance, Scenario}; @@ -26,23 +25,19 @@ pub const PHOENIX_ETERNAL_PROGRAM_ID: Pubkey = pub const PHOENIX_GLOBAL_CONFIG: Pubkey = Pubkey::from_str_const("2zskx2iyCvb6Stg7RBZkt1f6MrF4dpYtMG3yMvKwqtUZ"); -const TRADER_HEADER_LEN: usize = size_of::(); -const COLLATERAL_BYTE_RANGE: core::ops::Range = 88..96; -const COLLATERAL_FIELD: &str = "quote_lot_collateral"; +pub fn is_phoenix_trader_account(data: &[u8]) -> bool { + data.len() >= 8 + && PhoenixAccount::from_discriminant(data[..8].try_into().unwrap()) + == Some(PhoenixAccount::Trader) +} + +const COLLATERAL_FIELD: &str = "traderState.quoteLotCollateral"; const DIRECT_MARK_SYMBOL_FIELD: &str = "symbol"; const DIRECT_MARK_TICKS_FIELD: &str = "target_ticks"; const REFERENCE_SPOT_TICKS_FIELD: &str = "spot_ticks"; const REFERENCE_PERP_TICKS_FIELD: &str = "perp_ticks"; const PREPARATION_SLOT: u64 = 0; const MARK_PRICE_RANGE: core::ops::Range = 16..32; -const MARK_PRICE_TICKS_RANGE: core::ops::Range = 24..32; - -#[derive(Clone, Debug, PartialEq)] -pub struct PhoenixCollateralPreparation { - pub scenario: Scenario, - pub trader: Pubkey, - pub target_quote_lots: i64, -} pub fn phoenix_market_symbols( perp_asset_map: Pubkey, @@ -79,49 +74,21 @@ pub fn phoenix_market_symbols( } #[derive(Debug, Error, PartialEq, Eq)] -pub enum PhoenixTraderPatchError { - #[error("expected Phoenix Eternal owner, got {actual}")] - InvalidOwner { actual: Pubkey }, - #[error("invalid Phoenix Trader account: {0}")] - InvalidTrader(#[from] PhoenixAccountDecodeError), - #[error("collateral patch changed byte {offset} outside the collateral field")] - UnexpectedByteChange { offset: usize }, -} - -#[derive(Debug, Error, PartialEq, Eq)] -pub enum PhoenixDirectMarkPatchError { - #[error("expected Phoenix Eternal owner, got {actual}")] - InvalidOwner { actual: Pubkey }, - #[error("invalid Phoenix PerpAssetMap account: {0}")] - InvalidPerpAssetMap(#[from] PhoenixAccountDecodeError), - #[error("Phoenix market {symbol} was not found")] - MarketNotFound { symbol: String }, - #[error("target mark ticks {ticks} exceed the Phoenix u32 tick range")] - InvalidTicks { ticks: u64 }, - #[error("selected Phoenix market metadata does not occur exactly once")] - InvalidMetadataLocation, - #[error("direct mark patch changed byte {offset} outside the mark tick field")] - UnexpectedByteChange { offset: usize }, -} - -#[derive(Debug, Error, PartialEq, Eq)] -pub enum PhoenixReferencePricePatchError { +enum PhoenixPricePatchError { #[error("expected Phoenix Eternal owner, got {actual}")] InvalidOwner { actual: Pubkey }, #[error("invalid Phoenix PerpAssetMap account: {0}")] InvalidPerpAssetMap(#[from] PhoenixAccountDecodeError), #[error("Phoenix market {symbol} was not found")] MarketNotFound { symbol: String }, - #[error("reference ticks {ticks} exceed the Phoenix u32 tick range")] + #[error("price ticks {ticks} exceed the Phoenix u32 tick range")] InvalidTicks { ticks: u64 }, #[error("selected Phoenix market metadata does not occur exactly once")] InvalidMetadataLocation, - #[error("reference-price patch changed byte {offset} outside the spot/perp fields")] + #[error("price patch changed byte {offset} outside the requested price fields")] UnexpectedByteChange { offset: usize }, } -/// The offset of the first byte a patch changed outside the ranges it was -/// allowed to touch, if there is one. fn changed_byte_outside( original: &[u8], patched: &[u8], @@ -137,194 +104,96 @@ fn changed_byte_outside( .map(|(offset, _)| offset) } -pub fn patch_trader_collateral( - owner: &Pubkey, - data: &[u8], - target_quote_lots: i64, -) -> Result, PhoenixTraderPatchError> { - if owner != &PHOENIX_ETERNAL_PROGRAM_ID { - return Err(PhoenixTraderPatchError::InvalidOwner { actual: *owner }); - } - - validate_trader(data)?; - - let mut header = TraderHeader::try_read_from_account_bytes(data)?; - header.trader_state.quote_lot_collateral = SignedQuoteLots::new(target_quote_lots); - - let mut patched_data = data.to_vec(); - patched_data[..TRADER_HEADER_LEN].copy_from_slice(bytemuck::bytes_of(&header)); - - validate_trader(&patched_data)?; - - if let Some(offset) = changed_byte_outside(data, &patched_data, &[COLLATERAL_BYTE_RANGE]) { - return Err(PhoenixTraderPatchError::UnexpectedByteChange { offset }); - } - - Ok(patched_data) -} - -pub fn patch_direct_mark( - owner: &Pubkey, - data: &[u8], - symbol: &str, - target_ticks: u64, -) -> Result, PhoenixDirectMarkPatchError> { - patch_direct_mark_inner(owner, data, symbol, target_ticks, None) -} - -fn patch_direct_mark_at_slot( +fn patch_direct_mark( owner: &Pubkey, data: &[u8], symbol: &str, target_ticks: u64, mark_slot: u64, -) -> Result, PhoenixDirectMarkPatchError> { - patch_direct_mark_inner(owner, data, symbol, target_ticks, Some(mark_slot)) -} - -fn patch_direct_mark_inner( - owner: &Pubkey, - data: &[u8], - symbol: &str, - target_ticks: u64, - mark_slot: Option, -) -> Result, PhoenixDirectMarkPatchError> { +) -> Result, PhoenixPricePatchError> { if owner != &PHOENIX_ETERNAL_PROGRAM_ID { - return Err(PhoenixDirectMarkPatchError::InvalidOwner { actual: *owner }); + return Err(PhoenixPricePatchError::InvalidOwner { actual: *owner }); } - let target_ticks = Ticks::new_checked(target_ticks).map_err(|_| { - PhoenixDirectMarkPatchError::InvalidTicks { + let target_ticks = + Ticks::new_checked(target_ticks).map_err(|_| PhoenixPricePatchError::InvalidTicks { ticks: target_ticks, - } - })?; - let map = PerpAssetMap::try_from_account_bytes(data)?; - let entry = - map.find_by_symbol(symbol)? - .ok_or_else(|| PhoenixDirectMarkPatchError::MarketNotFound { - symbol: symbol.to_string(), - })?; - let metadata_bytes = entry.metadata.as_bytes(); - let metadata_offset = unique_subslice_offset(data, metadata_bytes) - .ok_or(PhoenixDirectMarkPatchError::InvalidMetadataLocation)?; - let price_len = size_of::(); - let mut price = bytemuck::pod_read_unaligned::(&metadata_bytes[..price_len]); - if let Some(mark_slot) = mark_slot { + })?; + patch_price_component(data, symbol, &[MARK_PRICE_RANGE], |price| { price.mark_price.price.slot = mark_slot; - } - price.mark_price.price.ticks = target_ticks; - - let mut patched_data = data.to_vec(); - patched_data[metadata_offset..metadata_offset + price_len] - .copy_from_slice(bytemuck::bytes_of(&price)); - PerpAssetMap::try_from_account_bytes(&patched_data)?; - - let mark_range = if mark_slot.is_some() { - MARK_PRICE_RANGE - } else { - MARK_PRICE_TICKS_RANGE - }; - let allowed_range = metadata_offset + mark_range.start..metadata_offset + mark_range.end; - if let Some(offset) = changed_byte_outside(data, &patched_data, &[allowed_range]) { - return Err(PhoenixDirectMarkPatchError::UnexpectedByteChange { offset }); - } - - Ok(patched_data) -} - -pub fn patch_reference_prices( - owner: &Pubkey, - data: &[u8], - symbol: &str, - spot_ticks: u64, - perp_ticks: u64, -) -> Result, PhoenixReferencePricePatchError> { - patch_reference_prices_inner(owner, data, symbol, spot_ticks, perp_ticks, None) + price.mark_price.price.ticks = target_ticks; + }) } -fn patch_reference_prices_at_slot( +fn patch_reference_prices( owner: &Pubkey, data: &[u8], symbol: &str, spot_ticks: u64, perp_ticks: u64, reference_slot: u64, -) -> Result, PhoenixReferencePricePatchError> { - patch_reference_prices_inner( - owner, - data, - symbol, - spot_ticks, - perp_ticks, - Some(reference_slot), - ) -} - -fn patch_reference_prices_inner( - owner: &Pubkey, - data: &[u8], - symbol: &str, - spot_ticks: u64, - perp_ticks: u64, - reference_slot: Option, -) -> Result, PhoenixReferencePricePatchError> { +) -> Result, PhoenixPricePatchError> { if owner != &PHOENIX_ETERNAL_PROGRAM_ID { - return Err(PhoenixReferencePricePatchError::InvalidOwner { actual: *owner }); + return Err(PhoenixPricePatchError::InvalidOwner { actual: *owner }); } let spot_ticks = Ticks::new_checked(spot_ticks) - .map_err(|_| PhoenixReferencePricePatchError::InvalidTicks { ticks: spot_ticks })?; + .map_err(|_| PhoenixPricePatchError::InvalidTicks { ticks: spot_ticks })?; let perp_ticks = Ticks::new_checked(perp_ticks) - .map_err(|_| PhoenixReferencePricePatchError::InvalidTicks { ticks: perp_ticks })?; - let map = PerpAssetMap::try_from_account_bytes(data)?; - let entry = map.find_by_symbol(symbol)?.ok_or_else(|| { - PhoenixReferencePricePatchError::MarketNotFound { - symbol: symbol.to_string(), + .map_err(|_| PhoenixPricePatchError::InvalidTicks { ticks: perp_ticks })?; + patch_price_component(data, symbol, &reference_value_ranges(), |price| { + for value in &mut price + .mark_price + .spot_price_component + .last_exchange_spot_price + { + value.slot = reference_slot; + value.ticks = spot_ticks; } - })?; + for value in &mut price + .mark_price + .perp_price_component + .last_exchange_perp_price + { + value.slot = reference_slot; + value.ticks = perp_ticks; + } + }) +} + +fn patch_price_component( + data: &[u8], + symbol: &str, + allowed: &[core::ops::Range], + update: impl FnOnce(&mut PriceComponent), +) -> Result, PhoenixPricePatchError> { + let map = PerpAssetMap::try_from_account_bytes(data)?; + let entry = + map.find_by_symbol(symbol)? + .ok_or_else(|| PhoenixPricePatchError::MarketNotFound { + symbol: symbol.to_string(), + })?; let metadata_bytes = entry.metadata.as_bytes(); let metadata_offset = unique_subslice_offset(data, metadata_bytes) - .ok_or(PhoenixReferencePricePatchError::InvalidMetadataLocation)?; + .ok_or(PhoenixPricePatchError::InvalidMetadataLocation)?; let price_len = size_of::(); let mut price = bytemuck::pod_read_unaligned::(&metadata_bytes[..price_len]); - for value in &mut price - .mark_price - .spot_price_component - .last_exchange_spot_price - { - if let Some(reference_slot) = reference_slot { - value.slot = reference_slot; - } - value.ticks = spot_ticks; - } - for value in &mut price - .mark_price - .perp_price_component - .last_exchange_perp_price - { - if let Some(reference_slot) = reference_slot { - value.slot = reference_slot; - } - value.ticks = perp_ticks; - } + update(&mut price); - let mut patched_data = data.to_vec(); - patched_data[metadata_offset..metadata_offset + price_len] + let mut patched = data.to_vec(); + patched[metadata_offset..metadata_offset + price_len] .copy_from_slice(bytemuck::bytes_of(&price)); - PerpAssetMap::try_from_account_bytes(&patched_data)?; - - let allowed_ranges = reference_value_ranges(metadata_offset, reference_slot.is_some()); - if let Some(offset) = changed_byte_outside(data, &patched_data, &allowed_ranges) { - return Err(PhoenixReferencePricePatchError::UnexpectedByteChange { offset }); + PerpAssetMap::try_from_account_bytes(&patched)?; + let allowed: Vec<_> = allowed + .iter() + .map(|range| metadata_offset + range.start..metadata_offset + range.end) + .collect(); + if let Some(offset) = changed_byte_outside(data, &patched, &allowed) { + return Err(PhoenixPricePatchError::UnexpectedByteChange { offset }); } - - Ok(patched_data) + Ok(patched) } -fn reference_value_ranges( - metadata_offset: usize, - include_slots: bool, -) -> Vec> { +fn reference_value_ranges() -> Vec> { let mark_offset = core::mem::offset_of!(PriceComponent, mark_price); - let ticks_offset = core::mem::offset_of!(TicksAtSlot, ticks); let spot_offset = mark_offset + core::mem::offset_of!(MarkPrice, spot_price_component) + core::mem::offset_of!(SpotPriceComponent, last_exchange_spot_price); @@ -335,14 +204,8 @@ fn reference_value_ranges( .into_iter() .flat_map(|component_offset| { (0..5).map(move |index| { - let value_start = - metadata_offset + component_offset + index * size_of::(); - if include_slots { - value_start..value_start + size_of::() - } else { - let ticks_start = value_start + ticks_offset; - ticks_start..ticks_start + size_of::() - } + let value_start = component_offset + index * size_of::(); + value_start..value_start + size_of::() }) }) .collect() @@ -374,158 +237,72 @@ fn unique_subslice_offset(data: &[u8], needle: &[u8]) -> Option { match_offset } -/// Routes a Phoenix-owned account's override to the right typed patcher by the -/// value group it carries; the patchers themselves enforce the account type. pub fn forge_phoenix_override( account_pubkey: &Pubkey, account: &Account, account_values: &HashMap, materialization_slot: u64, ) -> SurfpoolResult> { - let wants_collateral = account_values.contains_key(COLLATERAL_FIELD); let wants_direct_mark = account_values.contains_key(DIRECT_MARK_TICKS_FIELD); let wants_reference = account_values.contains_key(REFERENCE_SPOT_TICKS_FIELD) || account_values.contains_key(REFERENCE_PERP_TICKS_FIELD); - - match (wants_collateral, wants_direct_mark, wants_reference) { - (true, false, false) => { - forge_trader_collateral_override(account_pubkey, account, account_values) - } - (false, true, false) => forge_direct_mark_override( - account_pubkey, - account, - account_values, - materialization_slot, - ), - (false, false, true) => forge_reference_price_override( - account_pubkey, - account, - account_values, - materialization_slot, - ), - _ => Err(SurfpoolError::internal( - "Phoenix overrides accept exactly one value group: quote_lot_collateral, \ + let fields: &[&str] = match (wants_direct_mark, wants_reference) { + (true, false) => &[DIRECT_MARK_SYMBOL_FIELD, DIRECT_MARK_TICKS_FIELD], + (false, true) => &[ + DIRECT_MARK_SYMBOL_FIELD, + REFERENCE_SPOT_TICKS_FIELD, + REFERENCE_PERP_TICKS_FIELD, + ], + _ => { + return Err(SurfpoolError::internal( + "Phoenix map overrides accept exactly one value group: \ symbol + target_ticks, or symbol + spot_ticks + perp_ticks", - )), - } -} - -pub fn forge_trader_collateral_override( - account_pubkey: &Pubkey, - account: &Account, - account_values: &HashMap, -) -> SurfpoolResult> { - if account_values.len() != 1 || !account_values.contains_key(COLLATERAL_FIELD) { - return Err(SurfpoolError::internal( - "Phoenix Trader collateral overrides accept only quote_lot_collateral", - )); - } - - let target_quote_lots = account_values[COLLATERAL_FIELD] - .as_str() - .ok_or_else(invalid_quote_lot_collateral) - .and_then(parse_quote_lot_collateral)?; - - patch_trader_collateral(&account.owner, &account.data, target_quote_lots).map_err(|error| { - match error { - PhoenixTraderPatchError::InvalidOwner { .. } => { - SurfpoolError::invalid_account_owner(account_pubkey, Some(error)) - } - _ => SurfpoolError::invalid_account_data( - account_pubkey, - "Expected a valid Phoenix Eternal Trader account", - Some(error), - ), - } - }) -} - -pub fn forge_direct_mark_override( - account_pubkey: &Pubkey, - account: &Account, - account_values: &HashMap, - materialization_slot: u64, -) -> SurfpoolResult> { - if account_values.len() != 2 - || !account_values.contains_key(DIRECT_MARK_SYMBOL_FIELD) - || !account_values.contains_key(DIRECT_MARK_TICKS_FIELD) - { - return Err(SurfpoolError::internal( - "Phoenix direct mark overrides accept only symbol and target_ticks", - )); - } - - let symbol = account_values[DIRECT_MARK_SYMBOL_FIELD] - .as_str() - .filter(|value| !value.is_empty()) - .ok_or_else(|| SurfpoolError::internal("symbol must be a non-empty string"))?; - let target_ticks = account_values[DIRECT_MARK_TICKS_FIELD] - .as_str() - .and_then(|value| value.parse::().ok()) - .ok_or_else(|| { - SurfpoolError::internal( - "target_ticks must be an unsigned 64-bit integer encoded as a string", - ) - })?; - - patch_direct_mark_at_slot( - &account.owner, - &account.data, - symbol, - target_ticks, - materialization_slot, - ) - .map_err(|error| match error { - PhoenixDirectMarkPatchError::InvalidOwner { .. } => { - SurfpoolError::invalid_account_owner(account_pubkey, Some(error)) + )); } - _ => SurfpoolError::invalid_account_data( - account_pubkey, - "Expected a valid Phoenix Eternal PerpAssetMap account", - Some(error), - ), - }) -} - -pub fn forge_reference_price_override( - account_pubkey: &Pubkey, - account: &Account, - account_values: &HashMap, - materialization_slot: u64, -) -> SurfpoolResult> { - if account_values.len() != 3 - || !account_values.contains_key(DIRECT_MARK_SYMBOL_FIELD) - || !account_values.contains_key(REFERENCE_SPOT_TICKS_FIELD) - || !account_values.contains_key(REFERENCE_PERP_TICKS_FIELD) + }; + if account_values.len() != fields.len() + || fields + .iter() + .any(|field| !account_values.contains_key(*field)) { - return Err(SurfpoolError::internal( - "Phoenix reference-price overrides accept only symbol, spot_ticks, and perp_ticks", - )); + return Err(SurfpoolError::internal(format!( + "Phoenix map overrides accept only {}", + fields.join(", ") + ))); } - let symbol = account_values[DIRECT_MARK_SYMBOL_FIELD] .as_str() .filter(|value| !value.is_empty()) .ok_or_else(|| SurfpoolError::internal("symbol must be a non-empty string"))?; - let spot_ticks = parse_unsigned_ticks( - &account_values[REFERENCE_SPOT_TICKS_FIELD], - REFERENCE_SPOT_TICKS_FIELD, - )?; - let perp_ticks = parse_unsigned_ticks( - &account_values[REFERENCE_PERP_TICKS_FIELD], - REFERENCE_PERP_TICKS_FIELD, - )?; - - patch_reference_prices_at_slot( - &account.owner, - &account.data, - symbol, - spot_ticks, - perp_ticks, - materialization_slot, - ) - .map_err(|error| match error { - PhoenixReferencePricePatchError::InvalidOwner { .. } => { + let patched = if wants_direct_mark { + patch_direct_mark( + &account.owner, + &account.data, + symbol, + parse_unsigned_ticks( + &account_values[DIRECT_MARK_TICKS_FIELD], + DIRECT_MARK_TICKS_FIELD, + )?, + materialization_slot, + ) + } else { + patch_reference_prices( + &account.owner, + &account.data, + symbol, + parse_unsigned_ticks( + &account_values[REFERENCE_SPOT_TICKS_FIELD], + REFERENCE_SPOT_TICKS_FIELD, + )?, + parse_unsigned_ticks( + &account_values[REFERENCE_PERP_TICKS_FIELD], + REFERENCE_PERP_TICKS_FIELD, + )?, + materialization_slot, + ) + }; + patched.map_err(|error| match error { + PhoenixPricePatchError::InvalidOwner { .. } => { SurfpoolError::invalid_account_owner(account_pubkey, Some(error)) } _ => SurfpoolError::invalid_account_data( @@ -540,36 +317,13 @@ pub fn build_phoenix_collateral_scenario( trader: Pubkey, trader_account: &Account, target_quote_lots: &str, -) -> SurfpoolResult { + global_trader_index: Option<&Account>, +) -> SurfpoolResult { let target_quote_lots = parse_quote_lot_collateral(target_quote_lots)?; - patch_trader_collateral( - &trader_account.owner, - &trader_account.data, - target_quote_lots, - ) - .map_err(|error| match error { - PhoenixTraderPatchError::InvalidOwner { .. } => { - SurfpoolError::invalid_account_owner(trader, Some(error)) - } - _ => SurfpoolError::invalid_account_data( - trader, - "Expected a valid Phoenix Eternal Trader account", - Some(error), - ), - })?; + let header = super::collateral::trader_header(&trader, trader_account)?; - // Collateral is a claim on the global vault's real tokens. An override cannot - // mint that backing, so raising it would let withdrawals draw on balances that - // are not there; a real DepositFunds transaction is the way up. - let current_quote_lots = TraderHeader::try_read_from_account_bytes(&trader_account.data) - .map(|header| header.trader_state.quote_lot_collateral.as_inner()) - .map_err(|error| { - SurfpoolError::invalid_account_data( - trader, - "Expected a valid Phoenix Eternal Trader account", - Some(error), - ) - })?; + // Raising collateral needs a real deposit into the global vault. + let current_quote_lots = super::collateral::effective_collateral(&header, global_trader_index)?; if target_quote_lots > current_quote_lots { return Err(SurfpoolError::internal(format!( "Phoenix collateral stress can only lower collateral: {current_quote_lots} quote lots \ @@ -596,7 +350,7 @@ pub fn build_phoenix_collateral_scenario( let mut scenario = Scenario::new( "Phoenix Trader Collateral Stress".to_string(), - "Set exact signed quote-lot collateral on a validated Phoenix Eternal Trader account." + "Set exact signed quote-lot collateral on a Phoenix Trader and its effective index entry." .to_string(), ); scenario.tags = vec![ @@ -606,14 +360,22 @@ pub fn build_phoenix_collateral_scenario( ]; scenario.add_override(collateral_override); - Ok(PhoenixCollateralPreparation { - scenario, - trader, - target_quote_lots, - }) + Ok(scenario) } pub fn phoenix_perp_asset_map_address(global_account: &Account) -> SurfpoolResult { + Ok(Pubkey::new_from_array( + phoenix_global_config(global_account)?.perp_asset_map_key(), + )) +} + +pub fn phoenix_global_trader_index_address(global_account: &Account) -> SurfpoolResult { + Ok(Pubkey::new_from_array( + phoenix_global_config(global_account)?.global_trader_index_header_key(), + )) +} + +fn phoenix_global_config(global_account: &Account) -> SurfpoolResult { if global_account.owner != PHOENIX_ETERNAL_PROGRAM_ID { return Err(SurfpoolError::invalid_account_owner( PHOENIX_GLOBAL_CONFIG, @@ -634,7 +396,7 @@ pub fn phoenix_perp_asset_map_address(global_account: &Account) -> SurfpoolResul None::, )); } - Ok(Pubkey::new_from_array(global.perp_asset_map_key())) + Ok(global) } fn parse_unsigned_ticks(value: &serde_json::Value, field: &str) -> SurfpoolResult { @@ -649,23 +411,11 @@ fn parse_unsigned_ticks(value: &serde_json::Value, field: &str) -> SurfpoolResul } fn parse_quote_lot_collateral(value: &str) -> SurfpoolResult { - value - .parse::() - .map_err(|_| invalid_quote_lot_collateral()) -} - -fn invalid_quote_lot_collateral() -> SurfpoolError { - SurfpoolError::internal( - "quote_lot_collateral must be a signed 64-bit integer encoded as a string", - ) -} - -fn validate_trader(data: &[u8]) -> Result<(), PhoenixAccountDecodeError> { - let mut aligned_words = vec![0_u64; data.len().div_ceil(size_of::())]; - let aligned_bytes = bytemuck::cast_slice_mut::(&mut aligned_words); - aligned_bytes[..data.len()].copy_from_slice(data); - Trader::try_from_account_bytes(&aligned_bytes[..data.len()])?; - Ok(()) + value.parse::().map_err(|_| { + SurfpoolError::internal( + "quote_lot_collateral must be a signed 64-bit integer encoded as a string", + ) + }) } #[cfg(test)] @@ -676,6 +426,8 @@ pub(crate) mod tests { use super::*; + const TRADER_HEADER_LEN: usize = size_of::(); + const COLLATERAL_BYTE_RANGE: core::ops::Range = 88..96; const POSITION_MAP_PREFIX_LEN: usize = 16; const POSITION_ENTRY_LEN: usize = 40; const PERP_ASSET_MAP_LEN: usize = 1_622_064; @@ -722,187 +474,39 @@ pub(crate) mod tests { } #[test] - fn patches_only_collateral_and_preserves_the_dynamic_tail() { - let data = trader_fixture(6_996_825_500, 1, 2); - let dynamic_tail = data[TRADER_HEADER_LEN..].to_vec(); - - let patched = patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data, 371_499_999) - .expect("valid collateral patch"); - - let header = TraderHeader::try_read_from_account_bytes(&patched).expect("valid header"); - assert_eq!( - header.trader_state.quote_lot_collateral.as_inner(), - 371_499_999 - ); - assert_eq!(patched.len(), data.len()); - assert_eq!(&patched[TRADER_HEADER_LEN..], dynamic_tail); - assert!( - data.iter() - .zip(&patched) - .enumerate() - .filter(|(_, (before, after))| before != after) - .all(|(offset, _)| COLLATERAL_BYTE_RANGE.contains(&offset)) - ); - } - - #[test] - fn accepts_i64_collateral_boundaries() { - let data = trader_fixture(0, 0, 0); - - for target in [i64::MIN, i64::MAX] { - let patched = - patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data, target).unwrap(); - let header = TraderHeader::try_read_from_account_bytes(&patched).unwrap(); - assert_eq!(header.trader_state.quote_lot_collateral.as_inner(), target); - } - } + fn builder_rejects_an_account_that_is_not_a_trader() { + let mut account = trader_account(); + account.data[..8].fill(0); - #[test] - fn rejects_the_wrong_owner() { - let error = patch_trader_collateral(&Pubkey::new_unique(), &trader_fixture(0, 0, 0), 1) + let error = build_phoenix_collateral_scenario(Pubkey::new_unique(), &account, "1", None) .unwrap_err(); - assert!(matches!( - error, - PhoenixTraderPatchError::InvalidOwner { .. } - )); - } - - #[test] - fn rejects_the_wrong_discriminant() { - let mut data = trader_fixture(0, 0, 0); - data[..8].fill(0); - - let error = patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data, 1).unwrap_err(); - - assert!(matches!( - error, - PhoenixTraderPatchError::InvalidTrader( - PhoenixAccountDecodeError::InvalidDiscriminant { .. } - ) - )); + assert!(error.to_string().contains("Trader")); } #[test] - fn rejects_a_truncated_header() { - let data = trader_fixture(0, 0, 0); - - let error = patch_trader_collateral( - &PHOENIX_ETERNAL_PROGRAM_ID, - &data[..TRADER_HEADER_LEN - 1], - 1, + fn builder_rejects_collateral_outside_the_signed_64_bit_range() { + let error = build_phoenix_collateral_scenario( + Pubkey::new_unique(), + &trader_account(), + "9223372036854775808", + None, ) .unwrap_err(); - assert!(matches!( - error, - PhoenixTraderPatchError::InvalidTrader( - PhoenixAccountDecodeError::AccountTooSmall { .. } - ) - )); - } - - #[test] - fn rejects_missing_allocated_position_capacity() { - let data = trader_fixture(0, 1, 2); - let truncated_len = data.len() - POSITION_ENTRY_LEN; - - let error = patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data[..truncated_len], 1) - .unwrap_err(); - - assert!(matches!( - error, - PhoenixTraderPatchError::InvalidTrader( - PhoenixAccountDecodeError::AccountTooSmall { .. } - ) - )); - } - - #[test] - fn rejects_position_length_above_capacity() { - let data = trader_fixture(2, 2, 1); - - let error = patch_trader_collateral(&PHOENIX_ETERNAL_PROGRAM_ID, &data, 1).unwrap_err(); - - assert!(matches!( - error, - PhoenixTraderPatchError::InvalidTrader(PhoenixAccountDecodeError::InvalidData { - reason: "position map length exceeds capacity", - .. - }) - )); - } - - #[test] - fn override_accepts_only_the_collateral_field() { - let values = HashMap::from([ - (COLLATERAL_FIELD.to_string(), serde_json::json!("371499999")), - ("flags".to_string(), serde_json::json!("1")), - ]); - - let error = - forge_trader_collateral_override(&Pubkey::new_unique(), &trader_account(), &values) - .unwrap_err(); - - assert!(error.to_string().contains("accept only")); - } - - #[test] - fn override_rejects_json_numbers() { - let values = - HashMap::from([(COLLATERAL_FIELD.to_string(), serde_json::json!(371_499_999))]); - - let error = - forge_trader_collateral_override(&Pubkey::new_unique(), &trader_account(), &values) - .unwrap_err(); - - assert!(error.to_string().contains("encoded as a string")); - } - - #[test] - fn override_rejects_out_of_range_strings() { - let values = HashMap::from([( - COLLATERAL_FIELD.to_string(), - serde_json::json!("9223372036854775808"), - )]); - - let error = - forge_trader_collateral_override(&Pubkey::new_unique(), &trader_account(), &values) - .unwrap_err(); - assert!(error.to_string().contains("signed 64-bit integer")); } - #[test] - fn override_parses_exact_string_values() { - let values = HashMap::from([( - COLLATERAL_FIELD.to_string(), - serde_json::json!("-9007199254740993"), - )]); - - let patched = - forge_trader_collateral_override(&Pubkey::new_unique(), &trader_account(), &values) - .unwrap(); - let header = TraderHeader::try_read_from_account_bytes(&patched).unwrap(); - - assert_eq!( - header.trader_state.quote_lot_collateral.as_inner(), - -9_007_199_254_740_993 - ); - } - #[test] fn builds_one_editable_collateral_override_for_the_requested_trader() { let trader = Pubkey::new_unique(); let preparation = - build_phoenix_collateral_scenario(trader, &trader_account(), "-9007199254740993") + build_phoenix_collateral_scenario(trader, &trader_account(), "-9007199254740993", None) .unwrap(); - assert_eq!(preparation.trader, trader); - assert_eq!(preparation.target_quote_lots, -9_007_199_254_740_993); - assert_eq!(preparation.scenario.overrides.len(), 1); + assert_eq!(preparation.overrides.len(), 1); - let collateral_override = &preparation.scenario.overrides[0]; + let collateral_override = &preparation.overrides[0]; assert_eq!( collateral_override.template_id, "phoenix-trader-collateral-stress" @@ -919,18 +523,6 @@ pub(crate) mod tests { assert!(!collateral_override.fetch_before_use); } - #[test] - fn builder_rejects_an_invalid_target_before_creating_a_scenario() { - let error = build_phoenix_collateral_scenario( - Pubkey::new_unique(), - &trader_account(), - "9223372036854775808", - ) - .unwrap_err(); - - assert!(error.to_string().contains("signed 64-bit integer")); - } - #[test] fn builder_refuses_to_raise_collateral_above_its_vault_backing() { let trader = Pubkey::new_unique(); @@ -939,13 +531,13 @@ pub(crate) mod tests { ..trader_account() }; - let raised = build_phoenix_collateral_scenario(trader, &funded, "501").unwrap_err(); + let raised = build_phoenix_collateral_scenario(trader, &funded, "501", None).unwrap_err(); assert!(raised.to_string().contains("can only lower collateral")); - let lowered = build_phoenix_collateral_scenario(trader, &funded, "499").unwrap(); - assert_eq!(lowered.target_quote_lots, 499); - let held = build_phoenix_collateral_scenario(trader, &funded, "500").unwrap(); - assert_eq!(held.target_quote_lots, 500); + let lowered = build_phoenix_collateral_scenario(trader, &funded, "499", None).unwrap(); + assert_eq!(lowered.overrides[0].values[COLLATERAL_FIELD], "499"); + let held = build_phoenix_collateral_scenario(trader, &funded, "500", None).unwrap(); + assert_eq!(held.overrides[0].values[COLLATERAL_FIELD], "500"); } #[test] @@ -954,7 +546,7 @@ pub(crate) mod tests { let mut account = trader_account(); account.owner = Pubkey::new_unique(); - let error = build_phoenix_collateral_scenario(trader, &account, "1").unwrap_err(); + let error = build_phoenix_collateral_scenario(trader, &account, "1", None).unwrap_err(); assert!(error.to_string().contains("invalid account owner")); } @@ -977,15 +569,16 @@ pub(crate) mod tests { } #[test] - fn patches_only_the_selected_mark_ticks_in_an_official_market_entry() { + fn patches_only_the_selected_mark_ticks_and_slot_in_an_official_market_entry() { let data = perp_asset_map_fixture(); let map = PerpAssetMap::try_from_account_bytes(&data).unwrap(); let before = map.find_by_symbol("SOL").unwrap().unwrap(); let metadata_offset = unique_subslice_offset(&data, before.metadata.as_bytes()).unwrap(); - let patched = patch_direct_mark(&PHOENIX_ETERNAL_PROGRAM_ID, &data, "SOL", 1).unwrap(); + let patched = patch_direct_mark(&PHOENIX_ETERNAL_PROGRAM_ID, &data, "SOL", 1, 123).unwrap(); let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); let after = map.find_by_symbol("SOL").unwrap().unwrap(); + assert_eq!(after.metadata.oracle_price().mark_price.price.slot, 123); assert_eq!( after @@ -1004,8 +597,8 @@ pub(crate) mod tests { .enumerate() .filter(|(_, (before, after))| before != after) .all(|(offset, _)| { - (metadata_offset + MARK_PRICE_TICKS_RANGE.start - ..metadata_offset + MARK_PRICE_TICKS_RANGE.end) + (metadata_offset + MARK_PRICE_RANGE.start + ..metadata_offset + MARK_PRICE_RANGE.end) .contains(&offset) }) ); @@ -1031,6 +624,7 @@ pub(crate) mod tests { "SOL", spot_ticks, perp_ticks, + 123, ) .unwrap(); let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); @@ -1044,7 +638,7 @@ pub(crate) mod tests { .spot_price_component .last_exchange_spot_price .iter() - .all(|value| value.ticks.as_inner() == spot_ticks) + .all(|value| value.ticks.as_inner() == spot_ticks && value.slot == 123) ); assert!( price @@ -1052,91 +646,19 @@ pub(crate) mod tests { .perp_price_component .last_exchange_perp_price .iter() - .all(|value| value.ticks.as_inner() == perp_ticks) + .all(|value| value.ticks.as_inner() == perp_ticks && value.slot == 123) ); assert_eq!(patched.len(), data.len()); } } - #[test] - fn reference_price_patch_refreshes_each_reference_slot() { - let data = perp_asset_map_fixture(); - let patched = patch_reference_prices_at_slot( - &PHOENIX_ETERNAL_PROGRAM_ID, - &data, - "SOL", - 8_000, - 7_000, - 123, - ) - .unwrap(); - let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); - let entry = map.find_by_symbol("SOL").unwrap().unwrap(); - let price = entry.metadata.oracle_price(); - - assert!( - price - .mark_price - .spot_price_component - .last_exchange_spot_price - .iter() - .all(|value| value.slot == 123 && value.ticks.as_inner() == 8_000) - ); - assert!( - price - .mark_price - .perp_price_component - .last_exchange_perp_price - .iter() - .all(|value| value.slot == 123 && value.ticks.as_inner() == 7_000) - ); - } - - #[test] - fn reference_price_override_requires_exact_string_fields() { - let account = Account { - lamports: 1, - data: perp_asset_map_fixture(), - owner: PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }; - let values = HashMap::from([ - ( - DIRECT_MARK_SYMBOL_FIELD.to_string(), - serde_json::json!("SOL"), - ), - ( - REFERENCE_SPOT_TICKS_FIELD.to_string(), - serde_json::json!("8000"), - ), - ( - REFERENCE_PERP_TICKS_FIELD.to_string(), - serde_json::json!("7000"), - ), - ]); - forge_reference_price_override(&Pubkey::new_unique(), &account, &values, 100).unwrap(); - - let mut numeric = values; - numeric.insert( - REFERENCE_SPOT_TICKS_FIELD.to_string(), - serde_json::json!(8000), - ); - assert!( - forge_reference_price_override(&Pubkey::new_unique(), &account, &numeric, 100) - .unwrap_err() - .to_string() - .contains("encoded as a string") - ); - } - #[test] fn direct_mark_rejects_unknown_markets_and_out_of_range_ticks() { let data = perp_asset_map_fixture(); assert!(matches!( - patch_direct_mark(&PHOENIX_ETERNAL_PROGRAM_ID, &data, "BTC", 1), - Err(PhoenixDirectMarkPatchError::MarketNotFound { .. }) + patch_direct_mark(&PHOENIX_ETERNAL_PROGRAM_ID, &data, "BTC", 1, 123), + Err(PhoenixPricePatchError::MarketNotFound { .. }) )); assert!(matches!( patch_direct_mark( @@ -1144,8 +666,9 @@ pub(crate) mod tests { &data, "SOL", u64::from(u32::MAX) + 1, + 123, ), - Err(PhoenixDirectMarkPatchError::InvalidTicks { .. }) + Err(PhoenixPricePatchError::InvalidTicks { .. }) )); } @@ -1167,7 +690,7 @@ pub(crate) mod tests { ]); let patched = - forge_direct_mark_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); + forge_phoenix_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); let price = map .find_by_symbol("SOL") @@ -1183,7 +706,7 @@ pub(crate) mod tests { let mut numeric_ticks = values; numeric_ticks.insert(DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!(1)); assert!( - forge_direct_mark_override(&Pubkey::new_unique(), &account, &numeric_ticks, 123) + forge_phoenix_override(&Pubkey::new_unique(), &account, &numeric_ticks, 123) .unwrap_err() .to_string() .contains("encoded as a string") @@ -1191,55 +714,7 @@ pub(crate) mod tests { } #[test] - fn forge_dispatches_collateral_values_to_the_trader_patcher() { - let values = - HashMap::from([(COLLATERAL_FIELD.to_string(), serde_json::json!("371499999"))]); - - let patched = - forge_phoenix_override(&Pubkey::new_unique(), &trader_account(), &values, 100).unwrap(); - let header = TraderHeader::try_read_from_account_bytes(&patched).unwrap(); - - assert_eq!( - header.trader_state.quote_lot_collateral.as_inner(), - 371_499_999 - ); - } - - #[test] - fn forge_dispatches_symbol_and_target_ticks_to_the_direct_mark_patcher() { - let account = Account { - lamports: 1, - data: perp_asset_map_fixture(), - owner: PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }; - let values = HashMap::from([ - ( - DIRECT_MARK_SYMBOL_FIELD.to_string(), - serde_json::json!("SOL"), - ), - (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), - ]); - - let patched = - forge_phoenix_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); - let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); - let price = map - .find_by_symbol("SOL") - .unwrap() - .unwrap() - .metadata - .oracle_price() - .mark_price - .price; - - assert_eq!(price.ticks.as_inner(), 1); - assert_eq!(price.slot, 123); - } - - #[test] - fn forge_dispatches_symbol_and_reference_ticks_to_the_reference_patcher() { + fn reference_override_patches_prices_and_requires_string_fields() { let account = Account { lamports: 1, data: perp_asset_map_fixture(), @@ -1284,6 +759,17 @@ pub(crate) mod tests { .iter() .all(|value| value.slot == 123 && value.ticks.as_inner() == 7_000) ); + let mut numeric = values; + numeric.insert( + REFERENCE_SPOT_TICKS_FIELD.to_string(), + serde_json::json!(8000), + ); + assert!( + forge_phoenix_override(&Pubkey::new_unique(), &account, &numeric, 100) + .unwrap_err() + .to_string() + .contains("encoded as a string") + ); } #[test] @@ -1302,8 +788,11 @@ pub(crate) mod tests { #[test] fn forge_rejects_mixed_value_groups() { let values = HashMap::from([ - (COLLATERAL_FIELD.to_string(), serde_json::json!("371499999")), (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), + ( + REFERENCE_SPOT_TICKS_FIELD.to_string(), + serde_json::json!("2"), + ), ]); let error = forge_phoenix_override(&Pubkey::new_unique(), &trader_account(), &values, 100) diff --git a/crates/core/src/scenarios/registry.rs b/crates/core/src/scenarios/registry.rs index f89c70778..ddcc90f9b 100644 --- a/crates/core/src/scenarios/registry.rs +++ b/crates/core/src/scenarios/registry.rs @@ -40,7 +40,7 @@ pub const PUMP_AMM_V1_OVERRIDES_CONTENT: &str = include_str!("./protocols/pump-amm/v1/overrides.yaml"); pub const PHOENIX_ETERNAL_IDL_CONTENT: &str = - include_str!("./protocols/phoenix-eternal/v1/editor-schema.json"); + include_str!("./protocols/phoenix-eternal/v1/idl.json"); pub const PHOENIX_ETERNAL_OVERRIDES_CONTENT: &str = include_str!("./protocols/phoenix-eternal/v1/overrides.yaml"); @@ -436,6 +436,35 @@ mod tests { ); } + #[test] + fn phoenix_market_templates_source_symbols_from_the_live_catalog_tool() { + let registry = TemplateRegistry::new(); + for id in [ + "phoenix-direct-mark-risk-shock", + "phoenix-reference-price-divergence", + ] { + let template = registry.get(id).expect("phoenix market template exists"); + let symbol = template + .properties + .iter() + .find(|property| property.path == "symbol") + .expect("market template has a symbol property"); + assert!( + symbol.is_dynamic_ref(), + "{id} symbol must be a dynamic_ref, not a hardcoded constant" + ); + assert_eq!( + symbol.source_name(), + Some("list_phoenix_markets"), + "{id} symbol options must come from the live catalog tool" + ); + assert!( + symbol.constant_name().is_none(), + "{id} symbol must not reference a static constant" + ); + } + } + #[test] fn test_registry_loads_all_protocols() { let registry = TemplateRegistry::new(); diff --git a/crates/core/src/surfnet/svm.rs b/crates/core/src/surfnet/svm.rs index e7f561f98..36d1fc634 100644 --- a/crates/core/src/surfnet/svm.rs +++ b/crates/core/src/surfnet/svm.rs @@ -93,8 +93,11 @@ use crate::{ rpc::utils::convert_transaction_metadata_from_canonical, scenarios::{ TemplateRegistry, - protocols::phoenix_eternal::v1::state_builder::{ - PHOENIX_ETERNAL_PROGRAM_ID, forge_phoenix_override, + protocols::phoenix_eternal::v1::{ + collateral::collateral_override_value, + state_builder::{ + PHOENIX_ETERNAL_PROGRAM_ID, forge_phoenix_override, is_phoenix_trader_account, + }, }, }, storage::{OverlayStorage, Storage, StorageBackend}, @@ -217,7 +220,6 @@ pub fn apply_override_to_decoded_account( let final_key = parts[parts.len() - 1]; match current { Value::Object(map) => { - // Convert serde_json::Value to txtx Value let txtx_value = json_to_txtx_value(value)?; map.insert(final_key.to_string(), txtx_value); Ok(()) @@ -2904,7 +2906,7 @@ impl SurfnetSvm { if !override_instance.values.is_empty() { // Filter out values that are only used for PDA derivation (not account data) let pda_refs = override_instance.account.get_pda_seed_references(); - let account_values: HashMap = override_instance + let mut account_values: HashMap = override_instance .values .iter() .filter(|(key, _)| !pda_refs.contains(key)) @@ -2936,6 +2938,22 @@ impl SurfnetSvm { continue; }; + let is_phoenix_trader = account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID + && is_phoenix_trader_account(account.data()); + if is_phoenix_trader { + if let Some(legacy) = account_values.remove("quote_lot_collateral") { + if account_values.contains_key("traderState.quoteLotCollateral") { + return Err(SurfpoolError::internal( + "Phoenix collateral override must use only traderState.quoteLotCollateral", + )); + } + account_values.insert("traderState.quoteLotCollateral".to_string(), legacy); + } + if let Some(value) = account_values.get_mut("traderState.quoteLotCollateral") { + *value = collateral_override_value(value)?; + } + } + // Mints fail the token unpack and keep flowing through the IDL path. if is_supported_token_program(account.owner()) { if let Ok(token_account) = TokenAccount::unpack(account.data()) { @@ -2954,9 +2972,8 @@ impl SurfnetSvm { } } - // Phoenix Eternal accounts are zero-copy: route them by owner through - // the typed codec, the same seam the token path uses. - if account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID { + // Trader collateral uses the IDL; market maps need the typed price codec. + if account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID && !is_phoenix_trader { let new_account_data = forge_phoenix_override( &account_pubkey, &account, @@ -3041,6 +3058,21 @@ impl SurfnetSvm { } }; + let index_update = if is_phoenix_trader + && account_values.contains_key("traderState.quoteLotCollateral") + { + self.prepare_phoenix_collateral_index_update( + &account_pubkey, + &account, + idl, + &account_values, + remote_ctx, + ) + .await? + } else { + None + }; + // Create a new account with modified data let modified_account = Account { lamports: account.lamports(), @@ -3050,6 +3082,16 @@ impl SurfnetSvm { rent_epoch: account.rent_epoch(), }; + if let Some((index_key, before, after)) = index_update { + self.inner.set_account(index_key, after)?; + if let Err(error) = self.inner.set_account(account_pubkey, modified_account) { + self.inner.set_account(index_key, before)?; + return Err(error); + } + patched_this_slot.extend([index_key, account_pubkey]); + continue; + } + // Update the account in the SVM if let Err(e) = self.inner.set_account(account_pubkey, modified_account) { warn!( @@ -3071,6 +3113,74 @@ impl SurfnetSvm { Ok(()) } + async fn phoenix_dependency( + &self, + address: &Pubkey, + remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>, + ) -> SurfpoolResult { + if let Some(account) = self.inner.get_account(address)? { + return Ok(account); + } + if self.offline_accounts.contains_key(&address.to_string())? + || self + .offline_accounts + .get(&PHOENIX_ETERNAL_PROGRAM_ID.to_string())? + .is_some_and(|config| config.include_owned_accounts) + { + return Err(SurfpoolError::internal(format!( + "Phoenix dependency {address} is offline and missing locally" + ))); + } + let (client, commitment) = remote_ctx.as_ref().ok_or_else(|| { + SurfpoolError::internal(format!("Phoenix dependency {address} is missing locally")) + })?; + client + .get_account(address, *commitment) + .await? + .map_account() + } + + async fn prepare_phoenix_collateral_index_update( + &self, + trader: &Pubkey, + account: &Account, + idl: &Idl, + values: &HashMap, + remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>, + ) -> SurfpoolResult> { + use crate::scenarios::protocols::phoenix_eternal::v1::{ + collateral::{index_trader_state_range, trader_header}, + state_builder::{PHOENIX_GLOBAL_CONFIG, phoenix_global_trader_index_address}, + }; + let header = trader_header(trader, account)?; + if !header.trader_state.is_hot() { + return Ok(None); + } + let global = self + .phoenix_dependency(&PHOENIX_GLOBAL_CONFIG, remote_ctx) + .await?; + let index_key = phoenix_global_trader_index_address(&global)?; + let before = self.phoenix_dependency(&index_key, remote_ctx).await?; + let range = index_trader_state_range(&before, &header.key)?; + let encoded = Self::get_forged_idl_type_data( + &before.data[range.clone()], + idl, + "TraderState", + &HashMap::from([( + "quoteLotCollateral".to_string(), + values["traderState.quoteLotCollateral"].clone(), + )]), + )?; + if encoded.len() != range.len() || encoded[8..] != before.data[range.start + 8..range.end] { + return Err(SurfpoolError::internal( + "Phoenix TraderState IDL must preserve the index record layout", + )); + } + let mut after = before.clone(); + after.data[range].copy_from_slice(&encoded); + Ok(Some((index_key, before, after))) + } + /// Forges account data by applying overrides to existing account data /// /// This function: @@ -3122,15 +3232,28 @@ impl SurfnetSvm { )) })?; - // Find the corresponding type definition + let encoded = + Self::get_forged_idl_type_data(serialized_data, idl, &account_def.name, overrides)?; + let mut result = discriminator.to_vec(); + result.extend_from_slice(&encoded); + Ok(result) + } + + fn get_forged_idl_type_data( + serialized_data: &[u8], + idl: &Idl, + type_name: &str, + overrides: &HashMap, + ) -> SurfpoolResult> { + // A type can also describe a record embedded in a dynamically addressed account. let account_type = idl .types .iter() - .find(|t| t.name == account_def.name) + .find(|t| t.name == type_name) .ok_or_else(|| { SurfpoolError::internal(format!( "Type definition for account '{}' not found in IDL", - account_def.name + type_name )) })?; @@ -3188,10 +3311,8 @@ impl SurfnetSvm { )) })?; - // Reconstruct the account data with discriminator and preserve any trailing bytes - let mut new_account_data = - Vec::with_capacity(8 + re_encoded_data.len() + leftover_bytes.len()); - new_account_data.extend_from_slice(discriminator); + // Preserve trailing data outside the IDL type. + let mut new_account_data = Vec::with_capacity(re_encoded_data.len() + leftover_bytes.len()); new_account_data.extend_from_slice(&re_encoded_data); new_account_data.extend_from_slice(leftover_bytes); diff --git a/crates/core/src/tests/phoenix/mod.rs b/crates/core/src/tests/phoenix/mod.rs index 27e8e2acd..6d3c5c9b1 100644 --- a/crates/core/src/tests/phoenix/mod.rs +++ b/crates/core/src/tests/phoenix/mod.rs @@ -49,8 +49,7 @@ use crate::{ TemplateRegistry, protocols::phoenix_eternal::v1::state_builder::{ PHOENIX_ETERNAL_PROGRAM_ID, PHOENIX_GLOBAL_CONFIG, build_phoenix_collateral_scenario, - patch_direct_mark, patch_reference_prices, patch_trader_collateral, - phoenix_market_symbols, phoenix_perp_asset_map_address, + forge_phoenix_override, phoenix_market_symbols, phoenix_perp_asset_map_address, }, }, surfnet::{locker::SurfnetSvmLocker, svm::SurfnetSvm}, @@ -147,31 +146,29 @@ async fn live_candidate(needs_position: bool) -> (Pubkey, Account) { Err(error) => panic!("failed to list live Phoenix traders: {error}"), }; - // Addresses come back in pubkey order, so a handful from the front is not a - // representative sample; scan in batches until one qualifies. - for batch in candidates.chunks(100).take(4) { - let pubkeys = batch.iter().map(|(pubkey, _)| *pubkey).collect::>(); - for (pubkey, account) in pubkeys.iter().zip(fetch(&pubkeys).await) { - let Ok(trader) = Trader::try_from_account_bytes(&account.data) else { - continue; - }; - let has_collateral = trader.header.trader_state.quote_lot_collateral.as_inner() > 0; - // A downward mark shock only threatens a long, so the risk scenarios need one: - // the shock direction is fixed, the trader is what we go looking for. - let holds_a_long = trader - .positions() - .any(|(_, position)| position.base_lot_position().as_inner() > 0); - if has_collateral && (!needs_position || holds_a_long) { - cache.insert(needs_position, (*pubkey, account.clone())); - return (*pubkey, account); - } + for (pubkey, account) in candidates { + let account = account.to_account().expect("live Trader account decodes"); + let Ok(trader) = Trader::try_from_account_bytes(&account.data) else { + continue; + }; + let has_collateral = trader.header.trader_state.quote_lot_collateral.as_inner() > 0; + // A downward mark shock only threatens a long, so the risk scenarios need one: + // the shock direction is fixed, the trader is what we go looking for. + let holds_a_long = trader + .positions() + .any(|(_, position)| position.base_lot_position().as_inner() > 0); + if has_collateral + && (!needs_position || (holds_a_long && trader.header.trader_state.is_hot())) + { + cache.insert(needs_position, (pubkey, account.clone())); + return (pubkey, account); } } panic!( "no eligible live candidate: no live Phoenix Trader read carries collateral{}", if needs_position { - " and a long position" + " and is hot with a long position" } else { "" } @@ -251,11 +248,18 @@ async fn overrides_on_live_accounts_touch_only_their_target_bytes() { .ticks .as_inner(); - let shocked = patch_direct_mark( - &map_account.owner, - &map_account.data, - &symbol, - live_mark / 2 + 1, + let materialization_slot = entry.metadata.oracle_price().mark_price.price.slot + 1; + let shocked = forge_phoenix_override( + &perp_asset_map, + &map_account, + &HashMap::from([ + ("symbol".to_string(), serde_json::json!(symbol)), + ( + "target_ticks".to_string(), + serde_json::json!((live_mark / 2 + 1).to_string()), + ), + ]), + materialization_slot, ) .expect("direct mark patch on the live map"); assert_eq!( @@ -266,16 +270,25 @@ async fn overrides_on_live_accounts_touch_only_their_target_bytes() { let mark_diffs = diff_indices(&shocked, &map_account.data); assert!( !mark_diffs.is_empty() && mark_diffs.len() <= 16, - "a mark shock writes one tick field, got {} changed bytes", + "a mark shock writes its ticks and slot, got {} changed bytes", mark_diffs.len() ); - let diverged = patch_reference_prices( - &map_account.owner, - &map_account.data, - &symbol, - live_mark * 2, - live_mark * 3, + let diverged = forge_phoenix_override( + &perp_asset_map, + &map_account, + &HashMap::from([ + ("symbol".to_string(), serde_json::json!(symbol)), + ( + "spot_ticks".to_string(), + serde_json::json!((live_mark * 2).to_string()), + ), + ( + "perp_ticks".to_string(), + serde_json::json!((live_mark * 3).to_string()), + ), + ]), + materialization_slot, ) .expect("reference price patch on the live map"); assert_eq!(diverged.len(), map_account.data.len()); @@ -286,8 +299,40 @@ async fn overrides_on_live_accounts_touch_only_their_target_bytes() { .all(|index| !mark_diffs.contains(index)), "reference divergence must preserve the mark price it diverges from" ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn collateral_idl_override_preserves_live_trader_layout() { + let (trader, account) = live_trader().await; - let _ = perp_asset_map; + let idl: anchor_lang_idl::types::Idl = + serde_json::from_str(crate::scenarios::registry::PHOENIX_ETERNAL_IDL_CONTENT) + .expect("phoenix idl parses as an anchor idl"); + let (svm, _events_rx, _geyser_rx) = + SurfnetSvm::new(crate::surfnet::svm::SurfnetSvmConfig::default()).unwrap(); + + let target: i64 = 12_345; + let mut overrides = HashMap::new(); + overrides.insert( + "traderState.quoteLotCollateral".to_string(), + crate::scenarios::protocols::phoenix_eternal::v1::collateral::collateral_override_value( + &serde_json::Value::String(target.to_string()), + ) + .unwrap(), + ); + + let forged = svm + .get_forged_account_data(&trader, &account.data, &idl, &overrides) + .expect("idl override path forges the trader account"); + + let header = TraderHeader::try_read_from_account_bytes(&forged).expect("forged header decodes"); + assert_eq!(header.trader_state.quote_lot_collateral.as_inner(), target); + assert_eq!(forged.len(), account.data.len()); + let diffs = diff_indices(&forged, &account.data); + assert!( + diffs.iter().all(|index| (88..96).contains(index)), + "collateral override changed bytes outside 88..96: {diffs:?}" + ); } #[tokio::test(flavor = "multi_thread")] @@ -317,31 +362,35 @@ async fn the_market_templates_address_the_live_perp_asset_map() { #[tokio::test(flavor = "multi_thread")] async fn collateral_stress_refuses_to_outrun_the_live_vault() { let (trader, account) = live_trader().await; - let live_collateral = TraderHeader::try_read_from_account_bytes(&account.data) - .expect("a discovered trader decodes") - .trader_state - .quote_lot_collateral - .as_inner(); + let global = fetch(&[PHOENIX_GLOBAL_CONFIG]).await.remove(0); + let index_key = crate::scenarios::protocols::phoenix_eternal::v1::state_builder::phoenix_global_trader_index_address(&global).unwrap(); + let index = fetch(&[index_key]).await.remove(0); + let header = TraderHeader::try_read_from_account_bytes(&account.data).unwrap(); + let live_collateral = + crate::scenarios::protocols::phoenix_eternal::v1::collateral::effective_collateral( + &header, + Some(&index), + ) + .unwrap(); - let lowered = build_phoenix_collateral_scenario(trader, &account, "1") + let lowered = build_phoenix_collateral_scenario(trader, &account, "1", Some(&index)) .expect("lowering collateral is state preparation"); - assert_eq!(lowered.target_quote_lots, 1); + assert_eq!( + lowered.overrides[0].values["traderState.quoteLotCollateral"], + "1" + ); - let raised = - build_phoenix_collateral_scenario(trader, &account, &(live_collateral + 1).to_string()) - .unwrap_err(); + let raised = build_phoenix_collateral_scenario( + trader, + &account, + &(live_collateral + 1).to_string(), + Some(&index), + ) + .unwrap_err(); assert!( raised.to_string().contains("can only lower collateral"), "raising collateral past its vault backing must be refused, got: {raised}" ); - - let patched = patch_trader_collateral(&account.owner, &account.data, 1) - .expect("collateral patch on a valid trader"); - let diffs = diff_indices(&patched, &account.data); - assert!( - diffs.iter().all(|index| (88..96).contains(index)), - "only the collateral field may change, got {diffs:?}" - ); } const HAWKEYE_VIEW_MARGIN_DISCRIMINANT: [u8; 8] = [0xb2, 0x0a, 0x7c, 0xad, 0xec, 0xd2, 0x75, 0x06]; @@ -378,7 +427,24 @@ async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { // Collateral stress produces the risk condition: a trader with an open position and // almost no collateral is liquidatable whichever way the position points. let (collateral_locker, graph) = phoenix_behavior_locker().await; + use crate::scenarios::protocols::phoenix_eternal::v1::collateral::index_trader_state_range; + + let account = |key: &Pubkey| { + collateral_locker + .with_svm_reader(|svm| svm.get_account(key)) + .unwrap() + .unwrap() + }; + let before_trader = account(&graph.trader); + let before_index = account(&graph.global_trader_index); + let header = TraderHeader::try_read_from_account_bytes(&before_trader.data).unwrap(); + assert!( + header.trader_state.is_hot(), + "the regression requires a hot trader" + ); + let range = index_trader_state_range(&before_index, &header.key).unwrap(); let before = hawkeye_margin(&collateral_locker, &graph); + assert!(before.collateral_quote_lots > 1); assert!( before.position_count > 0, "the discovered trader must hold a position for margin to mean anything" @@ -391,14 +457,16 @@ async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { ); assert_eq!(before.is_liquidatable, 0, "the fork starts healthy"); + let scenario = + build_phoenix_collateral_scenario(graph.trader, &before_trader, "1", Some(&before_index)) + .unwrap(); collateral_locker - .register_scenario( - phoenix_collateral_scenario(graph.trader, serde_json::json!("1"), false), - Some(100), - ) + .register_scenario(scenario, Some(graph.clock.slot)) .unwrap(); + assert_eq!(account(&graph.trader), before_trader); + assert_eq!(account(&graph.global_trader_index), before_index); collateral_locker - .materialize_overrides_for_slot(&None, 100) + .materialize_overrides_for_slot(&None, graph.clock.slot) .await .unwrap(); let after_collateral = hawkeye_margin(&collateral_locker, &graph); @@ -411,44 +479,47 @@ async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { "stressing collateral must lower what the risk engine can count on" ); + assert_eq!(after_collateral.is_liquidatable, 1); + let mut expected_trader = before_trader; + expected_trader.data[88..96].copy_from_slice(&1_i64.to_le_bytes()); + let mut expected_index = before_index; + expected_index.data[range.start..range.start + 8].copy_from_slice(&1_i64.to_le_bytes()); + assert_eq!(account(&graph.trader), expected_trader); + assert_eq!(account(&graph.global_trader_index), expected_index); + // The cascade prepares the same collateral at slot 0 and a mark shock at slot 1. What // the deployed program reads is asserted; whether this particular position liquidates // depends on its side, which the discovery does not choose. let (mark_locker, graph) = phoenix_behavior_locker().await; let (symbol, orderbook, spline) = graph.markets[0].clone(); - let (trader_account, global_account, perp_asset_map_account) = - mark_locker.with_svm_reader(|svm_reader| { - ( - svm_reader.get_account(&graph.trader).unwrap().unwrap(), - svm_reader - .get_account(&crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG) - .unwrap() - .unwrap(), - svm_reader - .get_account(&graph.perp_asset_map) - .unwrap() - .unwrap(), - ) - }); + let trader_account = mark_locker + .with_svm_reader(|svm| svm.get_account(&graph.trader)) + .unwrap() + .unwrap(); let prepared_collateral = hawkeye_margin(&mark_locker, &graph).collateral_quote_lots / 2; + let index_account = mark_locker + .with_svm_reader(|svm| svm.get_account(&graph.global_trader_index)) + .unwrap() + .unwrap(); // The cascade is the two templates across slots: the collateral tool's scenario at slot 0 // and the mark shock at slot 1, which is what a user composes in the editor. - let mut cascade = crate::scenarios::protocols::phoenix_eternal::v1::state_builder::build_phoenix_collateral_scenario( + let mut cascade = build_phoenix_collateral_scenario( graph.trader, &trader_account, &prepared_collateral.to_string(), + Some(&index_account), ) - .unwrap() - .scenario; + .unwrap(); let mut shock = phoenix_direct_mark_scenario(graph.perp_asset_map, &symbol, "1", false) .overrides .remove(0); shock.scenario_relative_slot = 1; cascade.add_override(shock); - let _ = (&global_account, &perp_asset_map_account); - mark_locker.register_scenario(cascade, Some(100)).unwrap(); mark_locker - .materialize_overrides_for_slot(&None, 100) + .register_scenario(cascade, Some(graph.clock.slot)) + .unwrap(); + mark_locker + .materialize_overrides_for_slot(&None, graph.clock.slot) .await .unwrap(); let before_mark = hawkeye_bbo_for_market(&graph, &mark_locker, orderbook, spline); @@ -458,48 +529,23 @@ async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { "stage 0 prepares the collateral the cascade was built with" ); assert_ne!(before_mark.mark_price_ticks, 1); + mark_locker.with_svm_writer(|svm| { + let mut clock = graph.clock.clone(); + clock.slot += 1; + svm.inner.set_sysvar(&clock); + }); mark_locker - .materialize_overrides_for_slot(&None, 101) + .materialize_overrides_for_slot(&None, graph.clock.slot + 1) .await .unwrap(); let after_mark = hawkeye_bbo_for_market(&graph, &mark_locker, orderbook, spline); assert_eq!( - after_mark.mark_price_ticks, 1, - "stage 1 shocks the mark the program itself reads" + after_mark.mark_price_last_updated_slot, + graph.clock.slot + 1 ); - - // Reference divergence moves the cached index away from the mark and leaves the mark. - let (reference_locker, graph) = phoenix_behavior_locker().await; - let (symbol, orderbook, spline) = graph.markets[0].clone(); - let before_reference = hawkeye_bbo_for_market(&graph, &reference_locker, orderbook, spline); - reference_locker - .register_scenario( - phoenix_reference_price_scenario( - graph.perp_asset_map, - &symbol, - "80000", - "120000", - false, - ), - Some(100), - ) - .unwrap(); - reference_locker - .materialize_overrides_for_slot(&None, 100) - .await - .unwrap(); - let after_reference = hawkeye_bbo_for_market(&graph, &reference_locker, orderbook, spline); assert_eq!( - after_reference.mark_price_ticks, before_reference.mark_price_ticks, - "the divergence preserves the mark it diverges from" - ); - assert_ne!( - after_reference.index_price_ticks, before_reference.index_price_ticks, - "the cached reference the program reads must move" - ); - assert_ne!( - after_reference.index_price_ticks, after_reference.mark_price_ticks, - "spot and perp references diverging from the mark is the whole scenario" + after_mark.mark_price_ticks, 1, + "stage 1 shocks the mark the program itself reads" ); // The second live market proves the preparations are not market-specific. @@ -509,11 +555,11 @@ async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { second_locker .register_scenario( phoenix_direct_mark_scenario(graph.perp_asset_map, &symbol, "1", false), - Some(100), + Some(graph.clock.slot), ) .unwrap(); second_locker - .materialize_overrides_for_slot(&None, 100) + .materialize_overrides_for_slot(&None, graph.clock.slot) .await .unwrap(); let after_second = hawkeye_bbo_for_market(&graph, &second_locker, orderbook, spline); @@ -521,6 +567,41 @@ async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { assert_eq!(after_second.mark_price_ticks, 1); } +#[tokio::test(flavor = "multi_thread")] +async fn reference_prices_change_hawkeye_index_on_live_markets() { + let graph = phoenix_live_graph().await; + for (symbol, orderbook, spline) in &graph.markets { + let (locker, _) = phoenix_behavior_locker().await; + let before = hawkeye_bbo_for_market(&graph, &locker, *orderbook, *spline); + locker + .register_scenario( + phoenix_reference_price_scenario( + graph.perp_asset_map, + symbol, + "80000", + "79000", + false, + ), + Some(graph.clock.slot), + ) + .unwrap(); + locker + .materialize_overrides_for_slot(&None, graph.clock.slot) + .await + .unwrap(); + let after = hawkeye_bbo_for_market(&graph, &locker, *orderbook, *spline); + println!( + "{symbol}: mark {} -> {}, index {} -> {} (target spot 80000, perp 79000)", + before.mark_price_ticks, + after.mark_price_ticks, + before.index_price_ticks, + after.index_price_ticks + ); + assert_eq!(after.mark_price_ticks, before.mark_price_ticks, "{symbol}"); + assert_eq!(after.index_price_ticks, 80000, "{symbol}"); + } +} + /// A surfnet holding the live Phoenix account graph and a discovered live trader, with the /// two SBF programs loaded. Nothing here looks for a market or trader in an interesting /// state: the collateral stress below produces the risk condition the assertions check. @@ -532,13 +613,11 @@ async fn phoenix_behavior_locker() -> (SurfnetSvmLocker, PhoenixLiveGraph) { let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); let locker = SurfnetSvmLocker::new(svm); locker.with_svm_writer(|svm_writer| { - let mut clock = svm_writer.inner.get_sysvar::(); - clock.slot = 0; - svm_writer.inner.set_sysvar(&clock); + svm_writer.inner.set_sysvar(&graph.clock); svm_writer .inner .svm - .add_program(crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, &eternal_program) + .add_program(PHOENIX_ETERNAL_PROGRAM_ID, &eternal_program) .unwrap(); svm_writer .inner @@ -559,65 +638,52 @@ async fn phoenix_live_graph() -> PhoenixLiveGraph { return cached.clone(); } - let global_account = fetch(&[ - crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG, - ]) - .await - .remove(0); - let global = phoenix_rise_accounts::global_config::GlobalConfig::try_from_account_bytes( - &global_account.data, - ) - .expect("live GlobalConfig decodes"); + let global_account = fetch(&[PHOENIX_GLOBAL_CONFIG]).await.remove(0); + let global = GlobalConfig::try_from_account_bytes(&global_account.data) + .expect("live GlobalConfig decodes"); let perp_asset_map = Pubkey::new_from_array(global.perp_asset_map_key()); let global_trader_index = Pubkey::new_from_array(global.global_trader_index_header_key()); let active_trader_buffer = Pubkey::new_from_array(global.active_trader_buffer_header_key()); - let supporting = fetch(&[perp_asset_map, global_trader_index, active_trader_buffer]).await; - let map = PerpAssetMap::try_from_account_bytes(&supporting[0].data) - .expect("live PerpAssetMap decodes"); - let symbols = - crate::scenarios::protocols::phoenix_eternal::v1::state_builder::phoenix_market_symbols( - perp_asset_map, - &supporting[0], - ) - .expect("live PerpAssetMap lists its markets"); - assert!( - symbols.len() >= 2, - "no eligible live candidate: the preparations claim to work for any market, which \ - needs two live ones to show, found {symbols:?}" - ); - + let map_account = fetch(&[perp_asset_map]).await.remove(0); + let map = + PerpAssetMap::try_from_account_bytes(&map_account.data).expect("live PerpAssetMap decodes"); let mut markets = Vec::new(); - let mut accounts = vec![ - ( - crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG, - global_account, - ), - (perp_asset_map, supporting[0].clone()), - (global_trader_index, supporting[1].clone()), - (active_trader_buffer, supporting[2].clone()), + let mut addresses = vec![ + PHOENIX_GLOBAL_CONFIG, + perp_asset_map, + global_trader_index, + active_trader_buffer, ]; - for symbol in symbols.into_iter().take(2) { + for symbol in ["SOL", "BTC"] { let entry = map - .find_by_symbol(&symbol) + .find_by_symbol(symbol) .expect("symbol lookup") - .expect("listed symbol"); + .expect("live SOL/BTC market"); let orderbook = Pubkey::new_from_array(entry.metadata.static_market_params().market_account); - let spline = phoenix_rise_accounts::pda::derive_spline_collection_address( - &crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, - &orderbook, - ); - let market_accounts = fetch(&[orderbook, spline]).await; - accounts.push((orderbook, market_accounts[0].clone())); - accounts.push((spline, market_accounts[1].clone())); - markets.push((symbol, orderbook, spline)); + let spline = derive_spline_collection_address(&PHOENIX_ETERNAL_PROGRAM_ID, &orderbook); + addresses.extend([orderbook, spline]); + markets.push((symbol.to_string(), orderbook, spline)); } - let (trader, trader_account) = crate::tests::phoenix::live_trader_with_position().await; - accounts.push((trader, trader_account)); + let (trader, _) = live_trader_with_position().await; + addresses.push(trader); + addresses.push(Pubkey::from_str_const( + "SysvarC1ock11111111111111111111111111111111", + )); + // Read the clock and all dependencies from one bank, after address discovery. + let mut fresh = fetch(&addresses).await; + let clock: Clock = bincode::deserialize(&fresh.pop().unwrap().data).unwrap(); + assert!(clock.slot > 0); + let accounts = addresses.into_iter().zip(fresh).collect(); + println!( + "Phoenix live fork: slot={}, unix_timestamp={}", + clock.slot, clock.unix_timestamp + ); let graph = PhoenixLiveGraph { + clock, accounts, global_trader_index, active_trader_buffer, @@ -634,6 +700,7 @@ async fn phoenix_live_graph() -> PhoenixLiveGraph { /// margin view expects to be passed alongside them. #[derive(Clone)] struct PhoenixLiveGraph { + clock: Clock, accounts: Vec<(Pubkey, Account)>, global_trader_index: Pubkey, active_trader_buffer: Pubkey, @@ -644,97 +711,74 @@ struct PhoenixLiveGraph { markets: Vec<(String, Pubkey, Pubkey)>, } -fn hawkeye_margin(locker: &SurfnetSvmLocker, graph: &PhoenixLiveGraph) -> HawkeyeMarginView { +fn hawkeye_view( + locker: &SurfnetSvmLocker, + graph: &PhoenixLiveGraph, + discriminant: [u8; 8], + extra_accounts: &[Pubkey], +) -> Vec { let payer = Keypair::new(); - locker.with_svm_writer(|svm_writer| { - svm_writer - .inner - .airdrop(&payer.pubkey(), 1_000_000_000) - .unwrap(); - let instruction = Instruction { - program_id: HAWKEYE_PROGRAM_ID, - accounts: vec![ - AccountMeta::new_readonly( - crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, - false, - ), - AccountMeta::new_readonly( - crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG, - false, - ), - AccountMeta::new_readonly(graph.global_trader_index, false), - AccountMeta::new_readonly(graph.active_trader_buffer, false), - AccountMeta::new_readonly(graph.perp_asset_map, false), - AccountMeta::new_readonly(graph.trader, false), - ], - data: HAWKEYE_VIEW_MARGIN_DISCRIMINANT.to_vec(), - }; + let accounts = [ + PHOENIX_ETERNAL_PROGRAM_ID, + PHOENIX_GLOBAL_CONFIG, + graph.global_trader_index, + graph.active_trader_buffer, + graph.perp_asset_map, + ] + .iter() + .chain(extra_accounts) + .map(|address| AccountMeta::new_readonly(*address, false)) + .collect(); + locker.with_svm_writer(|svm| { + svm.inner.airdrop(&payer.pubkey(), 1_000_000_000).unwrap(); let transaction = Transaction::new_signed_with_payer( - // A live trader carries more positions than the fixture did, and the margin view - // walks all of them. &[ ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), - instruction, + Instruction { + program_id: HAWKEYE_PROGRAM_ID, + accounts, + data: discriminant.to_vec(), + }, ], Some(&payer.pubkey()), &[&payer], - svm_writer.inner.svm.latest_blockhash(), + svm.inner.svm.latest_blockhash(), ); - let metadata = svm_writer.inner.send_transaction(transaction).unwrap(); - let margin = - bytemuck::pod_read_unaligned::(&metadata.return_data.data); - assert_eq!(margin.magic, HAWKEYE_MARGIN_RETURN_MAGIC); - margin + svm.inner + .send_transaction(transaction) + .unwrap() + .return_data + .data }) } +fn hawkeye_margin(locker: &SurfnetSvmLocker, graph: &PhoenixLiveGraph) -> HawkeyeMarginView { + let data = hawkeye_view( + locker, + graph, + HAWKEYE_VIEW_MARGIN_DISCRIMINANT, + &[graph.trader], + ); + let margin = bytemuck::pod_read_unaligned::(&data); + assert_eq!(margin.magic, HAWKEYE_MARGIN_RETURN_MAGIC); + margin +} + fn hawkeye_bbo_for_market( graph: &PhoenixLiveGraph, locker: &SurfnetSvmLocker, orderbook: Pubkey, spline: Pubkey, ) -> HawkeyeBboView { - let payer = Keypair::new(); - locker.with_svm_writer(|svm_writer| { - svm_writer - .inner - .airdrop(&payer.pubkey(), 1_000_000_000) - .unwrap(); - let instruction = Instruction { - program_id: HAWKEYE_PROGRAM_ID, - accounts: vec![ - AccountMeta::new_readonly( - crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, - false, - ), - AccountMeta::new_readonly( - crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_GLOBAL_CONFIG, - false, - ), - AccountMeta::new_readonly(graph.global_trader_index, false), - AccountMeta::new_readonly(graph.active_trader_buffer, false), - AccountMeta::new_readonly(graph.perp_asset_map, false), - AccountMeta::new_readonly(orderbook, false), - AccountMeta::new_readonly(spline, false), - ], - data: HAWKEYE_VIEW_BBO_DISCRIMINANT.to_vec(), - }; - let transaction = Transaction::new_signed_with_payer( - // A live trader carries more positions than the fixture did, and the margin view - // walks all of them. - &[ - ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), - instruction, - ], - Some(&payer.pubkey()), - &[&payer], - svm_writer.inner.svm.latest_blockhash(), - ); - let metadata = svm_writer.inner.send_transaction(transaction).unwrap(); - let bbo = bytemuck::pod_read_unaligned::(&metadata.return_data.data); - assert_eq!(bbo.magic, HAWKEYE_BBO_RETURN_MAGIC); - bbo - }) + let data = hawkeye_view( + locker, + graph, + HAWKEYE_VIEW_BBO_DISCRIMINANT, + &[orderbook, spline], + ); + let bbo = bytemuck::pod_read_unaligned::(&data); + assert_eq!(bbo.magic, HAWKEYE_BBO_RETURN_MAGIC); + bbo } #[repr(C)] @@ -798,7 +842,7 @@ fn phoenix_collateral_scenario( surfpool_types::AccountAddress::Pubkey(trader.to_string()), ) .with_values(HashMap::from([( - "quote_lot_collateral".to_string(), + "traderState.quoteLotCollateral".to_string(), collateral, )])); instance.fetch_before_use = fetch_before_use; @@ -870,7 +914,7 @@ async fn materialize_patches_only_phoenix_trader_collateral() { Account { lamports: 1, data: base.clone(), - owner: crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + owner: PHOENIX_ETERNAL_PROGRAM_ID, executable: false, rent_epoch: 0, }, @@ -903,63 +947,11 @@ async fn materialize_patches_only_phoenix_trader_collateral() { ); } -#[tokio::test(flavor = "multi_thread")] -async fn materialize_applies_phoenix_collateral_without_fetch_before_use() { - let trader = Pubkey::new_unique(); - let scenario = phoenix_collateral_scenario(trader, serde_json::json!("371499999"), false); - let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); - let locker = SurfnetSvmLocker::new(svm); - locker.with_svm_writer(|svm_writer| { - svm_writer - .set_account( - &trader, - Account { - lamports: 1, - data: phoenix_trader_fixture(6_996_825_500), - owner: crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }, - ) - .unwrap(); - }); - - locker.register_scenario(scenario, Some(100)).unwrap(); - locker - .materialize_overrides_for_slot(&None, 100) - .await - .unwrap(); - - let after = locker - .with_svm_reader(|svm_reader| svm_reader.get_account(&trader)) - .unwrap() - .unwrap(); - let header = TraderHeader::try_read_from_account_bytes(&after.data).unwrap(); - assert_eq!( - header.trader_state.quote_lot_collateral.as_inner(), - 371_499_999 - ); -} - #[tokio::test(flavor = "multi_thread")] async fn materialize_applies_a_phoenix_direct_mark_override() { let perp_asset_map = Pubkey::new_unique(); let base = crate::scenarios::protocols::phoenix_eternal::v1::state_builder::tests::perp_asset_map_fixture(); - let mut scenario = surfpool_types::Scenario::new( - "Phoenix direct mark risk shock".to_string(), - "Phoenix direct mark override".to_string(), - ); - let mut instance = surfpool_types::OverrideInstance::new( - "phoenix-direct-mark-risk-shock".to_string(), - 0, - surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()), - ) - .with_values(HashMap::from([ - ("symbol".to_string(), serde_json::json!("SOL")), - ("target_ticks".to_string(), serde_json::json!("1")), - ])); - instance.fetch_before_use = false; - scenario.add_override(instance); + let scenario = phoenix_direct_mark_scenario(perp_asset_map, "SOL", "1", false); let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); let locker = SurfnetSvmLocker::new(svm); locker.with_svm_writer(|svm_writer| { @@ -969,7 +961,7 @@ async fn materialize_applies_a_phoenix_direct_mark_override() { Account { lamports: 1, data: base.clone(), - owner: crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + owner: PHOENIX_ETERNAL_PROGRAM_ID, executable: false, rent_epoch: 0, }, @@ -1033,7 +1025,7 @@ async fn materialize_refreshes_phoenix_reference_price_slots() { Account { lamports: 1, data: base, - owner: crate::scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + owner: PHOENIX_ETERNAL_PROGRAM_ID, executable: false, rent_epoch: 0, }, @@ -1041,12 +1033,6 @@ async fn materialize_refreshes_phoenix_reference_price_slots() { .unwrap(); }); - let mut scenario = scenario; - scenario.overrides[0].account = - surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()); - scenario.overrides[0] - .values - .insert("symbol".to_string(), serde_json::json!("SOL")); locker.register_scenario(scenario, Some(100)).unwrap(); locker .materialize_overrides_for_slot(&None, 100) diff --git a/crates/mcp/src/surfpool/mod.rs b/crates/mcp/src/surfpool/mod.rs index 4cad1f69b..4ea4108e7 100644 --- a/crates/mcp/src/surfpool/mod.rs +++ b/crates/mcp/src/surfpool/mod.rs @@ -19,7 +19,11 @@ use start_surfnet::StartSurfnetResponse; use surfpool_core::{ scenarios::{ TemplateRegistry, - protocols::phoenix_eternal::v1::state_builder::build_phoenix_collateral_scenario, + protocols::phoenix_eternal::v1::state_builder::{ + PHOENIX_GLOBAL_CONFIG, build_phoenix_collateral_scenario, + phoenix_global_trader_index_address, phoenix_market_symbols, + phoenix_perp_asset_map_address, + }, protocols::pump::v1::graduation_builder::{ build_pump_graduation_scenario, pump_graduation_addresses, }, @@ -167,6 +171,15 @@ pub struct CreatePhoenixCollateralScenarioParams { pub surfnet_port: Option, } +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct ListPhoenixMarketsParams { + #[schemars( + description = "The port of the target running local surfnet instance (e.g., 8899, 18899, 28899, etc.). Omit to use the default port, 8899." + )] + pub surfnet_port: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct StartSurfnetWithTokenAccountsParams { #[schemars( @@ -1040,7 +1053,7 @@ impl Surfpool { } #[tool( - description = "Creates an editable Phoenix Eternal Trader collateral-stress scenario. Requires a Trader pubkey and exact signed quote lots as a decimal string. The backend fetches and validates the live Trader account. This prepares risk state; it does not guarantee or execute liquidation." + description = "Creates an editable Phoenix Eternal Trader collateral-stress scenario. Requires a Trader pubkey and exact signed quote lots as a decimal string. Validates effective collateral in the Trader or its GlobalTraderIndex entry. Hot traders require a supported single-arena index. This prepares risk state; it does not guarantee or execute liquidation." )] async fn create_phoenix_collateral_scenario( &self, @@ -1055,7 +1068,7 @@ impl Surfpool { } }; let accounts = match self - .fetch_surfnet_accounts(params.surfnet_port, &[trader]) + .fetch_surfnet_accounts(params.surfnet_port, &[trader, PHOENIX_GLOBAL_CONFIG]) .await { Ok(accounts) => accounts, @@ -1066,16 +1079,88 @@ impl Surfpool { "Phoenix Trader account {trader} was not found" ))); }; - let preparation = match build_phoenix_collateral_scenario( + let Some(global_account) = accounts[1].as_ref() else { + return Ok(scenario_tool_error( + "Phoenix GlobalConfig was not found".to_string(), + )); + }; + let index_address = match phoenix_global_trader_index_address(global_account) { + Ok(address) => address, + Err(error) => return Ok(scenario_tool_error(error.to_string())), + }; + let index_accounts = match self + .fetch_surfnet_accounts(params.surfnet_port, &[index_address]) + .await + { + Ok(accounts) => accounts, + Err(error) => return Ok(scenario_tool_error(error)), + }; + let scenario = match build_phoenix_collateral_scenario( trader, trader_account, ¶ms.target_quote_lots, + index_accounts[0].as_ref(), ) { - Ok(preparation) => preparation, + Ok(scenario) => scenario, Err(error) => return Ok(scenario_tool_error(error.to_string())), }; - self.stage_scenario(preparation.scenario).await + self.stage_scenario(scenario).await + } + + #[tool( + description = "Lists the Phoenix Eternal perp markets currently listed on the live PerpAssetMap. Reads the fork's GlobalConfig and the map it points at, so the catalog reflects live state rather than a hardcoded snapshot. Use it to discover valid market symbols before preparing a Phoenix scenario." + )] + async fn list_phoenix_markets( + &self, + Parameters(params): Parameters, + ) -> Result { + let accounts = match self + .fetch_surfnet_accounts(params.surfnet_port, &[PHOENIX_GLOBAL_CONFIG]) + .await + { + Ok(accounts) => accounts, + Err(error) => return Ok(scenario_tool_error(error)), + }; + let Some(global_account) = accounts[0].as_ref() else { + return Ok(scenario_tool_error( + "Phoenix GlobalConfig account was not found on the surfnet".to_string(), + )); + }; + + let perp_asset_map = match phoenix_perp_asset_map_address(global_account) { + Ok(address) => address, + Err(error) => return Ok(scenario_tool_error(error.to_string())), + }; + + let map_accounts = match self + .fetch_surfnet_accounts(params.surfnet_port, &[perp_asset_map]) + .await + { + Ok(accounts) => accounts, + Err(error) => return Ok(scenario_tool_error(error)), + }; + let Some(map_account) = map_accounts[0].as_ref() else { + return Ok(scenario_tool_error(format!( + "Phoenix PerpAssetMap account {perp_asset_map} was not found on the surfnet" + ))); + }; + + let symbols = match phoenix_market_symbols(perp_asset_map, map_account) { + Ok(symbols) => symbols, + Err(error) => return Ok(scenario_tool_error(error.to_string())), + }; + + let payload = serde_json::json!({ + "perpAssetMap": perp_asset_map.to_string(), + "count": symbols.len(), + "symbols": symbols, + }); + let json = match serde_json::to_string(&payload) { + Ok(json) => json, + Err(error) => return Ok(scenario_tool_error(error.to_string())), + }; + Ok(CallToolResult::success(vec![Content::text(json)])) } #[tool( diff --git a/crates/types/src/scenarios.rs b/crates/types/src/scenarios.rs index f328773ec..6b4c9d2ab 100644 --- a/crates/types/src/scenarios.rs +++ b/crates/types/src/scenarios.rs @@ -257,6 +257,9 @@ pub enum PropertyKind { Field, /// A reference to a constant definition (renders as dropdown/combobox in UI) ConstantRef, + /// Options fetched live from an MCP tool (renders as a dropdown whose entries are + /// pulled from a running surfnet, so the catalog is never hardcoded) + DynamicRef, } /// Defines a property in a template with full metadata @@ -277,6 +280,9 @@ pub struct Property { /// For constant_ref type: the name of the constant definition to use #[serde(default, skip_serializing_if = "Option::is_none")] pub constant: Option, + /// For dynamic_ref type: the MCP tool whose result supplies the dropdown options + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source: Option, } impl Property { @@ -288,6 +294,7 @@ impl Property { label: None, description: None, constant: None, + source: None, } } @@ -299,6 +306,7 @@ impl Property { label: None, description: None, constant: Some(constant.into()), + source: None, } } @@ -307,11 +315,21 @@ impl Property { matches!(self.kind, PropertyKind::ConstantRef) } + /// Check if this is a dynamic reference + pub fn is_dynamic_ref(&self) -> bool { + matches!(self.kind, PropertyKind::DynamicRef) + } + /// Get the constant name if this is a constant reference pub fn constant_name(&self) -> Option<&str> { self.constant.as_deref() } + /// Get the source tool name if this is a dynamic reference + pub fn source_name(&self) -> Option<&str> { + self.source.as_deref() + } + /// Get the display label (falls back to path if no label set) pub fn display_label(&self) -> &str { self.label.as_deref().unwrap_or(&self.path) @@ -825,7 +843,7 @@ pub enum YamlProperty { Full { /// The path to the field in the IDL path: String, - /// The type of property: "field" (default) or "constant_ref" + /// The type of property: "field" (default), "constant_ref" or "dynamic_ref" #[serde(default, rename = "type")] kind: Option, /// Human-readable label for the UI (optional) @@ -837,6 +855,9 @@ pub enum YamlProperty { /// For constant_ref type: the name of the constant definition to use #[serde(default)] constant: Option, + /// For dynamic_ref type: the MCP tool whose result supplies the options + #[serde(default)] + source: Option, }, } @@ -850,9 +871,11 @@ impl From for Property { label, description, constant, + source, } => { let kind = match kind.as_deref() { Some("constant_ref") => PropertyKind::ConstantRef, + Some("dynamic_ref") => PropertyKind::DynamicRef, _ => PropertyKind::Field, }; Property { @@ -861,6 +884,7 @@ impl From for Property { label, description, constant, + source, } } } From 204c28a0b9b80ffafa5aa64621c953d94871ee78 Mon Sep 17 00:00:00 2001 From: 92Infinitus92 <92georgepetroff92@gmail.com> Date: Wed, 9 Sep 2026 13:36:50 +0300 Subject: [PATCH 3/6] fix(phoenix): skip rejected overrides instead of aborting materialization A Phoenix override with a bad value returned an error out of materialize_overrides_for_slot, which the block-production runloop turned into a simnet abort. Every Phoenix branch now warns with the override id and continues, like the shared IDL path. - dispatch the PerpAssetMap writer on the account discriminant, so the other Phoenix account types keep the generic IDL path - fill the fork gap once for GlobalConfig and the trader index instead of refetching per override - log the skipped same-slot refresh at warn - validate dynamic_ref values in create_scenario and say which tool resolves them - assert the templates' PerpAssetMap address against GlobalConfig in the live suite; keep every retry error in the live fetch helper --- .../protocols/phoenix-eternal/README.md | 3 +- .../phoenix-eternal/v1/collateral.rs | 13 ++- .../phoenix-eternal/v1/state_builder.rs | 12 ++- crates/core/src/surfnet/svm.rs | 90 +++++++++++++------ crates/core/src/tests/live.rs | 14 +-- crates/core/src/tests/phoenix/mod.rs | 15 ++++ crates/mcp/src/surfpool/mod.rs | 53 ++++++++++- 7 files changed, 153 insertions(+), 47 deletions(-) diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md index 5371fbc29..cd58870a1 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md @@ -66,7 +66,8 @@ does. The liquidation cascade combines those two templates in one scenario at sl only its Trader account. The builder's increase guard uses effective index collateral for hot traders, rather than the stale copy in their Trader account. - Hot collateral preparations currently support a single-arena `GlobalTraderIndex`. Missing, - corrupt, or multi-arena indexes fail before collateral writes. Node offsets are resolved + corrupt, or multi-arena indexes skip the override with a warning before any collateral write; + like every other override, a rejected value never stops block production. Node offsets are resolved again at Play; no trader address or node offset is pinned in the scenario. - Direct mark shock changes only the selected market's mark-price ticks and the mark-price slot, which is stamped with the slot the override materializes at so the program reads the new mark diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs index 0b4af74cb..5c615a0f9 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs @@ -411,7 +411,7 @@ mod tests { } #[tokio::test] - async fn materialization_rejects_invalid_or_missing_index_without_partial_trader_write() { + async fn materialization_skips_invalid_or_missing_index_without_partial_trader_write() { use super::super::state_builder::PHOENIX_GLOBAL_CONFIG; use crate::surfnet::svm::SurfnetSvm; @@ -449,12 +449,11 @@ mod tests { } svm.register_scenario(scenario, Some(100)).unwrap(); - assert!( - svm.materialize_overrides_for_slot(&None, 100) - .await - .is_err(), - "{failure}" - ); + // A rejected override is skipped, never an error out of the batch: that error + // would abort block production. + svm.materialize_overrides_for_slot(&None, 100) + .await + .unwrap_or_else(|error| panic!("{failure}: {error}")); assert_eq!( svm.get_account(&trader).unwrap().unwrap(), before_trader, diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs index 2e2990d00..9c871b260 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs @@ -26,9 +26,15 @@ pub const PHOENIX_GLOBAL_CONFIG: Pubkey = Pubkey::from_str_const("2zskx2iyCvb6Stg7RBZkt1f6MrF4dpYtMG3yMvKwqtUZ"); pub fn is_phoenix_trader_account(data: &[u8]) -> bool { - data.len() >= 8 - && PhoenixAccount::from_discriminant(data[..8].try_into().unwrap()) - == Some(PhoenixAccount::Trader) + phoenix_account_kind(data) == Some(PhoenixAccount::Trader) +} + +pub fn is_phoenix_perp_asset_map_account(data: &[u8]) -> bool { + phoenix_account_kind(data) == Some(PhoenixAccount::PerpAssetMap) +} + +fn phoenix_account_kind(data: &[u8]) -> Option { + PhoenixAccount::from_discriminant(data.get(..8)?.try_into().unwrap()) } const COLLATERAL_FIELD: &str = "traderState.quoteLotCollateral"; diff --git a/crates/core/src/surfnet/svm.rs b/crates/core/src/surfnet/svm.rs index 36d1fc634..b1763e015 100644 --- a/crates/core/src/surfnet/svm.rs +++ b/crates/core/src/surfnet/svm.rs @@ -96,7 +96,8 @@ use crate::{ protocols::phoenix_eternal::v1::{ collateral::collateral_override_value, state_builder::{ - PHOENIX_ETERNAL_PROGRAM_ID, forge_phoenix_override, is_phoenix_trader_account, + PHOENIX_ETERNAL_PROGRAM_ID, forge_phoenix_override, + is_phoenix_perp_asset_map_account, is_phoenix_trader_account, }, }, }, @@ -2810,9 +2811,9 @@ impl SurfnetSvm { // Fetch fresh account data from remote if requested if override_instance.fetch_before_use && patched_this_slot.contains(&account_pubkey) { - debug!( - "Skipping refresh for {}: an earlier override in this slot already patched it", - account_pubkey + warn!( + "Skipping refresh for {} (override {}): an earlier override in this slot already patched it", + account_pubkey, override_instance.id ); } else if override_instance.fetch_before_use { if let Some((client, _)) = remote_ctx { @@ -2938,19 +2939,29 @@ impl SurfnetSvm { continue; }; + // A bad value in one override is that override's failure, never the batch's: + // an error returned from this loop aborts block production. let is_phoenix_trader = account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID && is_phoenix_trader_account(account.data()); if is_phoenix_trader { if let Some(legacy) = account_values.remove("quote_lot_collateral") { if account_values.contains_key("traderState.quoteLotCollateral") { - return Err(SurfpoolError::internal( - "Phoenix collateral override must use only traderState.quoteLotCollateral", - )); + warn!( + "Skipping override {}: Phoenix collateral must use only traderState.quoteLotCollateral", + override_instance.id + ); + continue; } account_values.insert("traderState.quoteLotCollateral".to_string(), legacy); } if let Some(value) = account_values.get_mut("traderState.quoteLotCollateral") { - *value = collateral_override_value(value)?; + match collateral_override_value(value) { + Ok(parsed) => *value = parsed, + Err(e) => { + warn!("Skipping override {}: {}", override_instance.id, e); + continue; + } + } } } @@ -2972,14 +2983,26 @@ impl SurfnetSvm { } } - // Trader collateral uses the IDL; market maps need the typed price codec. - if account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID && !is_phoenix_trader { - let new_account_data = forge_phoenix_override( + // Trader collateral uses the IDL; the market map needs the typed price codec. + // Every other Phoenix account type stays on the generic IDL path. + if account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID + && is_phoenix_perp_asset_map_account(account.data()) + { + let new_account_data = match forge_phoenix_override( &account_pubkey, &account, &account_values, target_slot, - )?; + ) { + Ok(data) => data, + Err(e) => { + warn!( + "Skipping override {} for {}: {}", + override_instance.id, account_pubkey, e + ); + continue; + } + }; let modified_account = Account { lamports: account.lamports(), data: new_account_data, @@ -3061,14 +3084,25 @@ impl SurfnetSvm { let index_update = if is_phoenix_trader && account_values.contains_key("traderState.quoteLotCollateral") { - self.prepare_phoenix_collateral_index_update( - &account_pubkey, - &account, - idl, - &account_values, - remote_ctx, - ) - .await? + match self + .prepare_phoenix_collateral_index_update( + &account_pubkey, + &account, + idl, + &account_values, + remote_ctx, + ) + .await + { + Ok(update) => update, + Err(e) => { + warn!( + "Skipping override {} for {}: {}", + override_instance.id, account_pubkey, e + ); + continue; + } + } } else { None }; @@ -3114,7 +3148,7 @@ impl SurfnetSvm { } async fn phoenix_dependency( - &self, + &mut self, address: &Pubkey, remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>, ) -> SurfpoolResult { @@ -3134,14 +3168,17 @@ impl SurfnetSvm { let (client, commitment) = remote_ctx.as_ref().ok_or_else(|| { SurfpoolError::internal(format!("Phoenix dependency {address} is missing locally")) })?; - client + let account = client .get_account(address, *commitment) .await? - .map_account() + .map_account()?; + // Fill the fork gap once instead of refetching the same dependency per override. + self.inner.set_account(*address, account.clone())?; + Ok(account) } async fn prepare_phoenix_collateral_index_update( - &self, + &mut self, trader: &Pubkey, account: &Account, idl: &Idl, @@ -3251,10 +3288,7 @@ impl SurfnetSvm { .iter() .find(|t| t.name == type_name) .ok_or_else(|| { - SurfpoolError::internal(format!( - "Type definition for account '{}' not found in IDL", - type_name - )) + SurfpoolError::internal(format!("Type definition '{}' not found in IDL", type_name)) })?; // Set up generics for parsing diff --git a/crates/core/src/tests/live.rs b/crates/core/src/tests/live.rs index 853cb2b77..768dc1478 100644 --- a/crates/core/src/tests/live.rs +++ b/crates/core/src/tests/live.rs @@ -22,19 +22,21 @@ pub async fn fetch(addresses: &[Pubkey]) -> Vec { // The public endpoint throttles and intermittently 503s, which has nothing to do with what // the callers assert. Retry a few times with backoff so a transient refusal is not read as a // failure. - let mut attempt = 0; + let mut errors = Vec::new(); let results = loop { match client() .get_multiple_accounts(addresses, CommitmentConfig::confirmed()) .await { Ok(results) => break results, - Err(error) if attempt < 4 => { - attempt += 1; - tokio::time::sleep(std::time::Duration::from_millis(500 * attempt)).await; - let _ = error; + Err(error) => { + errors.push(error.to_string()); + if errors.len() > 4 { + panic!("failed to fetch {addresses:?} from mainnet: {errors:#?}"); + } + tokio::time::sleep(std::time::Duration::from_millis(500 * errors.len() as u64)) + .await; } - Err(error) => panic!("failed to fetch {addresses:?} from mainnet: {error}"), } }; diff --git a/crates/core/src/tests/phoenix/mod.rs b/crates/core/src/tests/phoenix/mod.rs index 6d3c5c9b1..6c4691f18 100644 --- a/crates/core/src/tests/phoenix/mod.rs +++ b/crates/core/src/tests/phoenix/mod.rs @@ -192,6 +192,21 @@ async fn live_accounts_satisfy_the_typed_layout_invariants() { Pubkey::new_from_array(global.perp_asset_map_key()), perp_asset_map ); + for id in [ + "phoenix-direct-mark-risk-shock", + "phoenix-reference-price-divergence", + ] { + let template_address = TemplateRegistry::new() + .get(id) + .unwrap_or_else(|| panic!("{id} template exists")) + .address + .resolve(None) + .unwrap_or_else(|| panic!("{id} carries a fixed address")); + assert_eq!( + template_address, perp_asset_map, + "{id} hardcodes the PerpAssetMap address; GlobalConfig says it moved" + ); + } assert_ne!( Pubkey::new_from_array(global.global_trader_index_header_key()), Pubkey::default() diff --git a/crates/mcp/src/surfpool/mod.rs b/crates/mcp/src/surfpool/mod.rs index 4ea4108e7..baae0c1f7 100644 --- a/crates/mcp/src/surfpool/mod.rs +++ b/crates/mcp/src/surfpool/mod.rs @@ -788,6 +788,7 @@ impl Surfpool { 2. `values` keys MUST be from the template's `properties` array 3. For PDA addresses, DO NOT provide `account` - it will be generated from template + values 4. For constant_ref properties (like feed_id), the value MUST come from search_constant_options results + 5. For dynamic_ref properties, the value MUST come from the tool named in the property's `source` (e.g. list_phoenix_markets) CORRECT JSON STRUCTURE FOR PYTH PRICE FEED: { @@ -893,6 +894,23 @@ impl Surfpool { // Validate constant_ref values against template constants for prop in &template.properties { + if prop.is_dynamic_ref() { + let present = override_instance + .values + .get(&prop.path) + .and_then(|value| value.as_str()) + .is_some_and(|value| !value.is_empty()); + if !present { + validation_errors.push(format!( + "Override '{}' (template '{}'): Missing required value for '{}'. Resolve it with the `{}` tool and pass it as a non-empty string.", + override_instance.id, + override_instance.template_id, + prop.path, + prop.source_name().unwrap_or("source") + )); + } + continue; + } if prop.is_constant_ref() { if let Some(constant_name) = prop.constant_name() { if let Some(constant_def) = template.constants.get(constant_name) { @@ -1164,7 +1182,7 @@ impl Surfpool { } #[tool( - description = "Lists all override templates as a light index: {id, name, description, protocol, accountType, tags, hasLlmContext}. Call this first to pick a templateId, then get_override_template for that one template's full detail (properties, address, llmContext). Constants are resolved with search_constant_options." + description = "Lists all override templates as a light index: {id, name, description, protocol, accountType, tags, hasLlmContext}. Call this first to pick a templateId, then get_override_template for that one template's full detail (properties, address, llmContext). Constants are resolved with search_constant_options; dynamic_ref properties with the tool named in their source." )] async fn get_override_templates(&self) -> Result { let registry = self.template_registry.read().map_err(|_| { @@ -1187,7 +1205,7 @@ impl Surfpool { } #[tool( - description = "Fetches one template's full detail (properties, address, constants summarized as {label, description, optionsCount}, and llmContext). Call after get_override_templates with the id you picked, before create_scenario. Resolve an actual constant option value with search_constant_options." + description = "Fetches one template's full detail (properties, address, constants summarized as {label, description, optionsCount}, and llmContext). Call after get_override_templates with the id you picked, before create_scenario. Resolve an actual constant option value with search_constant_options, and a dynamic_ref value with the tool named in its source." )] async fn get_override_template( &self, @@ -1849,6 +1867,37 @@ mod tests { } } + #[tokio::test] + async fn create_scenario_rejects_a_missing_dynamic_ref_value() { + let surfpool = Surfpool::new(); + let template = TemplateRegistry::new() + .get("phoenix-direct-mark-risk-shock") + .expect("template") + .clone(); + let mut scenario = surfpool_types::Scenario::new( + "no symbol".to_string(), + "a dynamic_ref without a value must be rejected".to_string(), + ); + scenario.add_override( + surfpool_types::OverrideInstance::new(template.id.clone(), 0, template.address) + .with_values(HashMap::from([( + "target_ticks".to_string(), + serde_json::json!("1"), + )])), + ); + let result = surfpool + .create_scenario(Parameters(scenario)) + .await + .unwrap(); + let text = &result.content[0].as_text().expect("text").text; + assert!( + text.contains("Missing required value") + && text.contains("symbol") + && text.contains("list_phoenix_markets"), + "the missing symbol and the tool that resolves it must be named, got: {text}" + ); + } + #[tokio::test] async fn create_scenario_rejects_bad_pump_overrides_before_any_http_call() { let surfpool = Surfpool::new(); From 72716ef84f9d8498e97532858e8ec0e7ce32ba16 Mon Sep 17 00:00:00 2001 From: 92Infinitus92 <92georgepetroff92@gmail.com> Date: Fri, 11 Sep 2026 17:28:37 +0300 Subject: [PATCH 4/6] fix(phoenix): validate trader identity and align scenario fields --- .../protocols/phoenix-eternal/README.md | 6 + .../phoenix-eternal/v1/collateral.rs | 92 +++++++- .../protocols/phoenix-eternal/v1/idl.json | 2 +- .../phoenix-eternal/v1/overrides.yaml | 9 +- .../phoenix-eternal/v1/state_builder.rs | 26 +- crates/core/src/scenarios/registry.rs | 25 ++ crates/core/src/surfnet/svm.rs | 14 +- crates/core/src/tests/phoenix/mod.rs | 10 +- crates/mcp/src/surfpool/mod.rs | 223 ++++++++++++++---- crates/types/src/scenarios.rs | 39 +++ 10 files changed, 361 insertions(+), 85 deletions(-) diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md index cd58870a1..fa34b07c6 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md @@ -15,6 +15,12 @@ for submitting trades, arbitrage, and liquidation transactions. Tick inputs are Phoenix protocol ticks, not human-readable USD prices. Pass tick and collateral values as decimal strings so values outside JavaScript's safe integer range remain exact. +Collateral overrides use `traderState.quoteLotCollateral`. The former `quote_lot_collateral` +name is rejected by MCP scenario creation; an existing scenario using it is skipped at Play with +an explicit warning naming the replacement. Update that scenario's values before playing it. +Market templates identify the real `PerpAssetMap` account. Their `symbol` and tick properties +carry `value_type: string` so Studio can render scenario inputs without synthetic IDL accounts. + ## Use from Studio 1. Start an online Surfpool fork and open Studio. diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs index 5c615a0f9..4bbafc018 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs @@ -1,4 +1,7 @@ -use std::{collections::HashSet, ops::Range}; +use std::{ + collections::{HashMap, HashSet}, + ops::Range, +}; use phoenix_rise_accounts::{ PhoenixAccount, PhoenixAccountDecodeError, multi_arena::MultiArenaHeader, trader::TraderHeader, @@ -16,13 +19,32 @@ pub fn trader_header(trader: &Pubkey, account: &Account) -> SurfpoolResult, )); } - TraderHeader::try_read_from_account_bytes(&account.data).map_err(|error| { + let header = TraderHeader::try_read_from_account_bytes(&account.data).map_err(|error| { SurfpoolError::invalid_account_data( trader, "Expected a valid Phoenix Eternal Trader account", Some(error), ) - }) + })?; + if Pubkey::new_from_array(header.key) != *trader { + return Err(SurfpoolError::invalid_account_data( + trader, + "Phoenix Trader header key does not match its account address", + None::, + )); + } + Ok(header) +} + +pub fn validate_collateral_fields( + values: &HashMap, +) -> SurfpoolResult<()> { + if values.contains_key("quote_lot_collateral") { + return Err(SurfpoolError::internal( + "Phoenix field quote_lot_collateral is no longer supported; use traderState.quoteLotCollateral as a decimal string", + )); + } + Ok(()) } pub fn index_trader_state_range( @@ -344,14 +366,6 @@ mod tests { "traderState.quoteLotCollateral", -9_007_199_254_740_993, ), - (FIRST_KEY, SECOND_KEY, 144, "quote_lot_collateral", 1), - ( - SECOND_KEY, - FIRST_KEY, - 208, - "quote_lot_collateral", - -9_007_199_254_740_993, - ), ] { let trader = Pubkey::new_from_array(key); let other_trader = Pubkey::new_from_array(other_key); @@ -420,6 +434,7 @@ mod tests { "missing key", "missing account", "conflicting fields", + "mismatched key", ] { let trader = Pubkey::new_from_array(FIRST_KEY); let index_key = Pubkey::new_unique(); @@ -431,6 +446,7 @@ mod tests { build_phoenix_collateral_scenario(trader, &before_trader, "1", Some(&before_index)) .unwrap(); match failure { + "mismatched key" => before_trader.data[24..56].copy_from_slice(&SECOND_KEY), "conflicting fields" => { scenario.overrides[0] .values @@ -467,4 +483,58 @@ mod tests { } } } + + #[test] + fn trader_idl_keeps_max_positions_and_preference_bits_separate() { + use crate::{scenarios::registry::PHOENIX_ETERNAL_IDL_CONTENT, surfnet::svm::SurfnetSvm}; + let trader = Pubkey::new_from_array(FIRST_KEY); + let mut account = trader_account(FIRST_KEY, 9_999, false); + account.data[112..116].copy_from_slice(&7_u32.to_le_bytes()); + account.data[116..120].copy_from_slice(&0xa5a5_5a5a_u32.to_le_bytes()); + let idl = serde_json::from_str(PHOENIX_ETERNAL_IDL_CONTENT).unwrap(); + let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + for (field, offset, target) in [ + ("maxPositions", 112, 11_u32), + ("traderPreferenceBits", 116, 0x1234_5678), + ] { + let forged = svm + .get_forged_account_data( + &trader, + &account.data, + &idl, + &HashMap::from([(field.to_string(), serde_json::json!(target))]), + ) + .unwrap(); + let mut expected = account.data.clone(); + expected[offset..offset + 4].copy_from_slice(&target.to_le_bytes()); + assert_eq!( + forged, expected, + "{field} must preserve all neighboring and trailing bytes" + ); + let header = TraderHeader::try_read_from_account_bytes(&forged).unwrap(); + assert_eq!(header.max_positions, if offset == 112 { target } else { 7 }); + assert_eq!( + header.trader_preference_bits, + if offset == 116 { target } else { 0xa5a5_5a5a } + ); + } + } + + #[test] + fn trader_header_rejects_an_embedded_key_for_another_account() { + let trader = Pubkey::new_from_array(FIRST_KEY); + for hot in [false, true] { + let account = trader_account(SECOND_KEY, 9_999, hot); + let error = trader_header(&trader, &account).unwrap_err(); + assert!( + error + .to_string() + .contains("key does not match its account address") + ); + assert!( + build_phoenix_collateral_scenario(trader, &account, "1", Some(&index_account())) + .is_err() + ); + } + } } diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/idl.json b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/idl.json index 36fc514a2..fd8fd147c 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/idl.json +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/idl.json @@ -1 +1 @@ -{"version": "0.2.0", "address": "EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih", "metadata": {"name": "Eternal", "version": "0.2.0", "spec": "0.1.0", "description": "Phoenix Eternal: a perpetual futures exchange", "address": "EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih", "origin": "anchor-compatible", "codama": {"nodesVersion": "1.6.0", "nodesFromAnchorVersion": "1.4.1", "renderersJsVersion": "2.2.0"}}, "docs": ["Anchor-compatible IDL for the Phoenix Eternal program.", "This IDL is maintained from the native Rust program surface and includes explicit instruction, account, and event discriminators for transaction and account decoding.", "PDA metadata is included where the program defines a canonical derivation; accounts without PDA metadata are supplied as explicit accounts or are stored in GlobalConfiguration."], "instructions": [{"name": "PlaceMarketOrder", "docs": ["Places an immediate-or-cancel order that consumes resting liquidity on the specified market."], "discriminator": [90, 118, 192, 252, 192, 99, 39, 145], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the market order."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}]}, {"name": "PlaceLimitOrder", "docs": ["Places a limit order that may rest on the orderbook or take liquidity depending on matching conditions."], "discriminator": [108, 176, 33, 186, 146, 229, 1, 197], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the limit order."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}]}, {"name": "PlaceMultiLimitOrder", "docs": ["Places multiple post-only limit orders in a single transaction, allowing batch order placement for market making."], "discriminator": [236, 208, 221, 172, 141, 226, 129, 84], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the batch of limit orders."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "MultipleOrderPacket"}}}]}, {"name": "CancelOrdersById", "docs": ["Cancels a specific set of resting orders identified by their FIFO order ids."], "discriminator": [234, 204, 126, 94, 222, 22, 141, 24], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderIds", "type": {"defined": {"name": "OrderIds"}}}]}, {"name": "CancelUpTo", "docs": ["Cancels resting orders on the specified side up to an optional order or price threshold."], "discriminator": [26, 209, 244, 253, 59, 175, 227, 54], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelUpToInstruction"}}}]}, {"name": "CancelAll", "docs": ["Cancels every resting order owned by the connected trader on the specified market."], "discriminator": [98, 191, 75, 220, 115, 40, 71, 237], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": []}, {"name": "UpdateOraclePricesWithOrdering", "docs": ["Updates oracle-provided spot and perp prices for one or more perpetual markets with monotonic timestamp ordering."], "discriminator": [164, 0, 14, 250, 58, 198, 25, 24], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA; provides oracle authority and quote decimals for conversions."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Wallet authorised to submit oracle price updates (root oracle or delegated user)."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account recording oracle capabilities and remaining allowances."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perpetual asset map storing mark price state and oracle metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateOraclePricesWithOrderingInstruction"}}}]}, {"name": "UpdateSplinePrice", "docs": ["Updates the mid-price for a spline market-making program on the specified market."], "discriminator": [45, 155, 200, 52, 37, 214, 187, 140], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Authority of the spline trader account authorised to update the spline price."]}, {"name": "traderAccount", "docs": ["Spline trader account whose price is being updated."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection PDA containing the spline configuration and pricing data."]}, {"name": "orderbook", "optional": true, "docs": ["Optional market orderbook account included by some clients as trailing metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplinePriceParamsWithOrdering"}}}]}, {"name": "DepositFunds", "docs": ["Deposits collateral tokens from a trader's token account into the global vault, crediting the trader's account."], "discriminator": [202, 39, 52, 211, 53, 20, 250, 88], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Authority of the trader account making the deposit."]}, {"name": "traderTokenAccount", "writable": true, "docs": ["Trader's token account from which funds are transferred."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA that receives the collateral credit."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account that receives the deposited tokens."]}, {"name": "tokenProgram", "docs": ["SPL Token program for executing the transfer."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "DepositFundsInstruction"}}}]}, {"name": "RegisterTrader", "docs": ["Registers a new trader account with the specified configuration and position capacity."], "discriminator": [75, 243, 224, 167, 1, 5, 51, 32], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that validates the trader registration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Account paying for the trader account creation and rent exemption."]}, {"name": "traderWallet", "docs": ["Authority that will control the trader account; used to derive the trader PDA."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA to be created; derived from [\"trader\", trader_wallet, [trader_pda_index, trader_subaccount_index]]."], "pda": {"seeds": [{"kind": "const", "value": [116, 114, 97, 100, 101, 114]}, {"kind": "account", "path": "traderWallet"}, {"kind": "arg", "path": "params.traderPdaIndex"}, {"kind": "arg", "path": "params.traderSubaccountIndex"}]}}, {"name": "systemProgram", "docs": ["System program for creating the trader account."]}], "args": [{"name": "params", "type": {"defined": {"name": "RegisterTraderParams"}}}]}, {"name": "LiquidateViaMarketOrder", "docs": ["Liquidates a trader's position by executing an immediate-or-cancel market order to reduce their exposure."], "discriminator": [251, 241, 184, 108, 70, 70, 127, 198], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and liquidation is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "liquidatorWallet", "signer": true, "docs": ["Liquidator authority executing the liquidation."]}, {"name": "liquidatedTrader", "writable": true, "docs": ["Trader account being liquidated (must be in liquidatable state)."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "LiquidationParams"}}}]}, {"name": "LiquidationTransfer", "docs": ["Transfers positions directly between a liquidator and liquidatee once the liquidatee is flagged as unhealthy."], "discriminator": [57, 218, 215, 68, 174, 23, 204, 127], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that owns the global trader index, active trader buffer, and collateral controls."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits, pricing, and funding parameters for every listed market."]}, {"name": "liquidator", "signer": true, "docs": ["Authority of the liquidator who receives transferred positions."]}, {"name": "liquidatorAccount", "writable": true, "docs": ["Liquidator trader account whose balances are credited as transfers are applied."]}, {"name": "liquidateeAccount", "writable": true, "docs": ["Liquidatee trader account whose unhealthy positions are reduced."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "LiquidationTransferParams"}}}]}, {"name": "CloseMatchedPositions", "docs": ["Closes offsetting positions between an at-loss trader and an in-profit trader during ADL."], "discriminator": [112, 174, 32, 120, 93, 113, 10, 161], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that authorizes ADL operations and tracks trader index PDAs."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account containing risk parameters used to validate the ADL transfer."]}, {"name": "caller", "signer": true, "docs": ["ADL authority invoking the matched close instruction."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "atLossAccount", "writable": true, "docs": ["Trader account currently at a loss whose matched exposure will be closed."]}, {"name": "inProfitAccount", "writable": true, "docs": ["Trader account with offsetting profitable exposure to receive the transfer."]}], "args": [{"name": "params", "type": {"defined": {"name": "ClosePositionsParams"}}}]}, {"name": "Log", "docs": ["Internal instruction used by the Phoenix log authority to emit events on-chain.", "This instruction is called via CPI and should not be invoked directly."], "discriminator": [141, 230, 214, 242, 9, 209, 207, 170], "accounts": [{"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA that must sign via CPI."], "signer": true, "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}], "args": [{"name": "eventBatch", "docs": ["Borsh event batch containing the batch index and MarketEvent vector."], "type": {"defined": {"name": "OffChainMarketEvent"}}}]}, {"name": "LogEventLengths", "docs": ["Internal instruction used by the Phoenix log authority to emit per-event byte lengths on-chain.", "This instruction is called via CPI and should not be invoked directly."], "discriminator": [247, 7, 134, 203, 181, 71, 153, 71], "accounts": [{"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA that must sign via CPI."], "signer": true, "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}], "args": [{"name": "eventLengths", "docs": ["Borsh event-length batch containing the batch index and serialized event lengths."], "type": {"defined": {"name": "OffChainMarketEventLengths"}}}]}, {"name": "WithdrawFunds", "docs": ["Withdraws collateral tokens from the global vault to a trader's token account, debiting the trader's account balance."], "discriminator": [241, 36, 29, 111, 208, 31, 104, 217], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Authority of the trader account initiating the withdrawal."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA from which collateral is debited."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map for margin and risk checks."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account from which tokens are withdrawn."]}, {"name": "destinationTokenAccount", "writable": true, "docs": ["Trader's destination token account to receive the withdrawn tokens."]}, {"name": "tokenProgram", "docs": ["SPL Token program for executing the transfer."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "withdrawQueue", "writable": true, "docs": ["Withdraw queue PDA tracking pending withdrawal requests."]}], "args": [{"name": "params", "type": {"defined": {"name": "WithdrawFundsInstruction"}}}]}, {"name": "UpdateSplineParameters", "docs": ["Updates spline curve parameters for automated market making on bid and ask sides."], "discriminator": [241, 91, 139, 170, 18, 41, 203, 22], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Authority authorized to update spline parameters."]}, {"name": "traderAccount", "docs": ["Trader account associated with the spline."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account holding bid and ask curve parameters."]}, {"name": "orderbook", "optional": true, "docs": ["Optional market orderbook account included by some clients as trailing metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplineParametersParamsWithOrdering"}}}]}, {"name": "PlaceStopLoss", "docs": ["Places a stop-loss order that triggers when the market price reaches the specified trigger price."], "discriminator": [236, 46, 164, 66, 235, 92, 162, 137], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer for creating the stop loss account."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet authority."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account PDA to be created or updated."]}, {"name": "systemProgram", "docs": ["System program for creating the stop loss account."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceStopLossInstruction"}}}]}, {"name": "ExecuteStopLoss", "docs": ["Executes a stop-loss order when the trigger conditions are met."], "discriminator": [38, 122, 90, 90, 182, 241, 7, 4], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signer", "signer": true, "docs": ["Signer authorized to execute stop loss orders."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account verifying execution authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "funder", "writable": true, "docs": ["Account to receive rent from closed stop loss account."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to execute and close."]}], "args": [{"name": "params", "type": {"defined": {"name": "ExecuteStopLossInstruction"}}}]}, {"name": "CancelStopLoss", "docs": ["Cancels a pending stop-loss order and closes the stop loss account."], "discriminator": [120, 201, 10, 102, 12, 9, 111, 126], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "funder", "writable": true, "docs": ["Account to receive rent from closed stop loss account."]}, {"name": "traderAccount", "docs": ["Trader account associated with the stop loss order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet authority."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to cancel and close."]}, {"name": "systemProgram", "docs": ["System program for closing the account."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelStopLossInstruction"}}}]}, {"name": "ForceCancelAll", "docs": ["Force cancels all open orders for a trader account. Requires authority authorization."], "discriminator": [201, 0, 188, 127, 41, 55, 80, 48], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority authorized to force cancel orders."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": []}, {"name": "ForceCancelRiskIncreasing", "docs": ["Force cancels risk-increasing orders for a trader to reduce margin exposure."], "discriminator": [41, 55, 217, 35, 92, 72, 42, 238], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "ForceCancelRiskIncreasingParams"}}}]}, {"name": "UpdateTraderState", "docs": ["Settles funding for a trader and evicts them from the active trader buffer when they no longer have resting orders."], "discriminator": [249, 139, 82, 44, 126, 66, 133, 220], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Read-only global configuration PDA used to verify the perp asset map and buffer keys."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA whose funding snapshot and active status are updated."]}, {"name": "perpAssetMap", "docs": ["Perp asset map providing funding parameters for settlement."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": []}, {"name": "ConsumeWithdrawQueue", "docs": ["Processes pending withdrawals from the withdraw queue."], "discriminator": [228, 40, 211, 139, 39, 212, 116, 113], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "withdrawQueue", "writable": true, "docs": ["Withdraw queue account."]}, {"name": "tokenProgram", "docs": ["SPL Token program."]}, {"name": "traderWallet", "docs": ["Trader wallet receiving the withdrawal."]}, {"name": "destinationTokenAccount", "writable": true, "docs": ["Destination token account for withdrawn funds."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}], "args": []}, {"name": "CreatePermission", "docs": ["Creates a new permission account PDA for delegating authority."], "discriminator": [190, 182, 26, 164, 156, 221, 8, 0], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer for account creation rent."]}, {"name": "permission", "writable": true, "docs": ["Permission account PDA to create."]}, {"name": "authority", "docs": ["Authority granting the permission."]}, {"name": "user", "docs": ["User receiving the permission."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "DelegateTrader", "docs": ["Delegates position authority of a trader account to another address."], "discriminator": [3, 164, 192, 106, 119, 91, 122, 224], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Current trader authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA to delegate."]}, {"name": "newPositionAuthority", "docs": ["New position authority address."]}], "args": []}, {"name": "RevokePermission", "docs": ["Revokes permission flags from a permission account."], "discriminator": [116, 82, 33, 181, 121, 144, 249, 227], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority revoking the permission."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission account for delegation."]}, {"name": "targetPermission", "writable": true, "docs": ["Permission account to revoke flags from."]}], "args": [{"name": "params", "type": {"defined": {"name": "RevokePermissionParams"}}}]}, {"name": "SetPermission", "docs": ["Sets permission flags on a permission account."], "discriminator": [70, 126, 41, 194, 245, 189, 128, 8], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "permission", "writable": true, "docs": ["Permission account to modify."]}, {"name": "authority", "signer": true, "docs": ["Authority owning the permission."]}, {"name": "user", "docs": ["User the permission applies to."]}], "args": [{"name": "params", "type": {"defined": {"name": "SetPermissionInstruction"}}}]}, {"name": "SetTraderCapabilitiesDelegated", "docs": ["Sets trader capability flags via a delegated permission."], "discriminator": [207, 170, 17, 21, 53, 35, 88, 151], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority with trader onboarding delegation."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account for delegation validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TraderCapabilityUpdate"}}}]}, {"name": "SetTraderCapability", "docs": ["Sets trader capability flags via risk authority."], "discriminator": [191, 72, 45, 190, 214, 250, 182, 213], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account for delegation validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TraderCapabilityUpdate"}}}]}, {"name": "SyncParentToChild", "docs": ["Syncs capabilities from a parent trader account to a child (isolated margin) account."], "discriminator": [175, 137, 217, 11, 235, 39, 150, 19], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "docs": ["Trader wallet owning both accounts."]}, {"name": "parentTraderAccount", "docs": ["Parent (cross margin) trader account."]}, {"name": "childTraderAccount", "writable": true, "docs": ["Child (isolated margin) trader account to sync."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}], "args": []}, {"name": "TransferCollateral", "docs": ["Transfers collateral between two trader accounts owned by the same wallet."], "discriminator": [157, 163, 63, 27, 242, 72, 251, 97], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet owning both accounts."]}, {"name": "srcTraderAccount", "writable": true, "docs": ["Source trader account."]}, {"name": "dstTraderAccount", "writable": true, "docs": ["Destination trader account."]}, {"name": "perpAssetMap", "docs": ["Perp asset map for margin calculations."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TransferCollateralInstruction"}}}]}, {"name": "TransferCollateralChildToParent", "docs": ["Transfers collateral from a child (isolated margin) account to its parent (cross margin) account."], "discriminator": [51, 100, 177, 115, 139, 135, 247, 139], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "docs": ["Trader wallet owning both accounts."]}, {"name": "childTraderAccount", "writable": true, "docs": ["Child (isolated margin) trader account."]}, {"name": "parentTraderAccount", "writable": true, "docs": ["Parent (cross margin) trader account."]}, {"name": "perpAssetMap", "docs": ["Perp asset map for margin calculations."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": []}, {"name": "UncrossCrank", "docs": ["Cranks the orderbook to uncross any crossed orders."], "discriminator": [196, 121, 94, 253, 139, 42, 252, 147], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account."]}], "args": [{"name": "params", "type": {"defined": {"name": "UncrossCrankParams"}}}]}, {"name": "ClearExpiredOrders", "docs": ["Clears expired orders from the orderbook as a maintenance crank operation."], "discriminator": [128, 95, 102, 169, 146, 205, 248, 94], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "ClearExpiredOrdersParams"}}}]}, {"name": "CreateConditionalOrdersAccount", "docs": ["Allocates the trader's conditional-orders PDA so further conditional-order placements can store entries."], "discriminator": [201, 6, 112, 61, 231, 15, 57, 248], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Funds the rent for the new conditional-orders PDA."]}, {"name": "traderWallet", "docs": ["Trader authority wallet; not signed here so a sponsorship server may pay rent."]}, {"name": "traderAccount", "docs": ["Trader state PDA whose key seeds the conditional-orders PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Empty account that will be initialised as the conditional-orders collection."]}, {"name": "systemProgram", "docs": ["System program for PDA creation."]}], "args": [{"name": "params", "type": {"defined": {"name": "CreateConditionalOrdersAccountInstruction"}}}]}, {"name": "PlacePositionConditionalOrder", "docs": ["Places a position-based conditional order that fires when mark price crosses the configured trigger."], "discriminator": [65, 108, 83, 129, 76, 193, 92, 143], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any account growth required by the conditional-orders PDA."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA; updated as part of risk validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA for the market."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet authorising the conditional order."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "systemProgram", "docs": ["System program (used for any required reallocations)."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlacePositionConditionalOrderInstruction"}}}]}, {"name": "CancelConditionalOrder", "docs": ["Cancels one or both trigger-order halves of a conditional-order entry."], "discriminator": [82, 104, 25, 51, 248, 54, 66, 184], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA owning the conditional order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelConditionalOrderInstruction"}}}]}, {"name": "PlaceAttachedConditionalOrder", "docs": ["Attaches a conditional order to an existing resting limit order so it fires when that order is filled."], "discriminator": [43, 117, 136, 128, 72, 150, 101, 122], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA owning the parent resting order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the parent limit order."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any conditional-orders PDA reallocation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "systemProgram", "docs": ["System program for PDA reallocation."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceAttachedConditionalOrderInstruction"}}}]}, {"name": "PlaceLimitOrderWithConditionals", "docs": ["Places a limit order and atomically attaches greater/less-than trigger orders to the resulting resting order."], "discriminator": [95, 45, 68, 168, 232, 218, 210, 92], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any conditional-orders PDA reallocation."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "systemProgram", "docs": ["System program for PDA reallocation."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceLimitOrderWithConditionalsInstruction"}}}]}, {"name": "ExecuteConditionalOrders", "docs": ["Authority-driven execution of a triggered conditional order."], "discriminator": [101, 214, 110, 0, 190, 226, 177, 236], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority (or delegate) authorised to execute conditional orders."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission/delegation PDA validated against the authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "ExecuteConditionalOrderInstruction"}}}]}, {"name": "PingConditionalOrders", "docs": ["Authority-driven sweep that activates or invalidates a trader's conditional orders based on current state."], "discriminator": [135, 170, 166, 34, 208, 230, 91, 64], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority (or delegate) authorised to ping conditional orders."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission/delegation PDA."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": []}, {"name": "CancelAllPlusConditional", "docs": ["Cancels every resting order for a trader on a market and clears the matching conditional orders."], "discriminator": [142, 173, 238, 126, 79, 19, 53, 103], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": []}, {"name": "MarketClosedForceCancelAll", "docs": ["Force cancels all open orders and conditional orders for a trader after a market has closed."], "discriminator": [229, 101, 143, 249, 150, 175, 175, 219], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Optional conditional orders account for the trader; clears stop loss and trigger state when present."]}], "args": []}, {"name": "ForceCancelAfterHours", "docs": ["Force cancels orders that are invalid during commodity after-hours trading."], "discriminator": [147, 13, 224, 61, 248, 234, 64, 164], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": []}, {"name": "AuthorizedForceCancel", "docs": ["Force cancels orders under the configured cancel authority until the requested side sizes remain."], "discriminator": [13, 22, 221, 234, 211, 165, 172, 2], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "cancelAuthority", "signer": true, "docs": ["Configured cancel authority signer."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "AuthorizedForceCancelParams"}}}]}, {"name": "AuthorizedForceCancelById", "docs": ["Force cancels specific bid and ask orders under the configured cancel authority."], "discriminator": [122, 159, 12, 159, 144, 108, 225, 241], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "cancelAuthority", "signer": true, "docs": ["Configured cancel authority signer."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "AuthorizedForceCancelByIdParams"}}}]}, {"name": "ChangeMarketStatus", "docs": ["Changes an orderbook market status through the market authority path."], "discriminator": [221, 127, 224, 41, 177, 145, 126, 8], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account whose market status is updated."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map used to validate and update market metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "ChangeMarketStatusParams"}}}]}, {"name": "CreateEscrowAccount", "docs": ["Creates or initializes the escrow PDA for a trader wallet."], "discriminator": [146, 147, 225, 47, 51, 64, 112, 1], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer funding escrow account creation."]}, {"name": "traderWallet", "docs": ["Trader wallet that owns the escrow account."]}, {"name": "traderEscrow", "writable": true, "docs": ["Escrow account PDA for the trader wallet."]}, {"name": "systemProgram", "docs": ["System program for creating the escrow account."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "CreateEscrowAccountParams"}}}]}, {"name": "CreateEscrowRequest", "docs": ["Creates an escrow request between sender and receiver trader accounts."], "discriminator": [116, 164, 236, 204, 187, 132, 195, 219], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "signer": true, "docs": ["Sender wallet signing the escrow request."]}, {"name": "senderTraderAccount", "docs": ["Sender trader account whose authority is validated."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account authorizing delegated escrow request creation."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverTraderAccount", "docs": ["Receiver trader account whose authority is validated."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account holding pending requests."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "CreateEscrowRequestParams"}}}]}, {"name": "AcceptEscrowRequest", "docs": ["Accepts a pending escrow request and applies its transfer actions."], "discriminator": [182, 41, 62, 227, 228, 175, 22, 137], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "docs": ["Sender wallet for the pending escrow request."]}, {"name": "senderTraderAccount", "writable": true, "docs": ["Sender trader account affected by accepted escrow actions."]}, {"name": "receiverWallet", "signer": true, "docs": ["Receiver wallet accepting the escrow request."]}, {"name": "receiverTraderAccount", "writable": true, "docs": ["Receiver trader account affected by accepted escrow actions."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account containing the pending request."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": []}, {"name": "CancelEscrowRequest", "docs": ["Cancels a pending escrow request by sequence number."], "discriminator": [202, 26, 163, 239, 32, 8, 85, 2], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerWallet", "signer": true, "docs": ["Sender or receiver wallet authorized to cancel the escrow request."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account containing the request."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelEscrowRequestParams"}}}]}, {"name": "UpsertEscrowRequest", "docs": ["Creates or increments a cash escrow request for a sender and receiver pair."], "discriminator": [213, 56, 86, 93, 34, 103, 160, 99], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "signer": true, "docs": ["Sender wallet signing the escrow upsert."]}, {"name": "senderTraderAccount", "docs": ["Sender trader account whose authority is validated."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account authorizing delegated escrow request creation."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverTraderAccount", "docs": ["Receiver trader account whose authority is validated."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account holding pending requests."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpsertEscrowRequestParams"}}}]}, {"name": "RegisterSpline", "docs": ["Registers a trader as an active spline participant for a market."], "discriminator": [30, 176, 160, 79, 252, 117, 241, 58], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "payerAccount", "writable": true, "signer": true, "docs": ["Payer funding any required spline account growth."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account for the market."]}, {"name": "marketAccount", "docs": ["Market orderbook account for the spline."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account being registered as a spline participant."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for isolated-only validation."]}, {"name": "systemProgram", "docs": ["System program for rent transfers or account growth."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "RegisterSplineParams"}}}]}, {"name": "UpdateCommodityMarketState", "docs": ["Updates commodity market state and related mark price inputs."], "discriminator": [45, 245, 97, 184, 200, 147, 142, 73], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or commodity-market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account updated with commodity market state."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account for the commodity market."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for the commodity market."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateCommodityMarketStateInstruction"}}}]}, {"name": "UpdatePerpIsolatedOnly", "docs": ["Updates the isolated-only flag for a perp asset."], "discriminator": [101, 56, 163, 250, 73, 35, 115, 91], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpIsolatedOnlyInstruction"}}}]}, {"name": "UpdatePerpLeverageTiers", "docs": ["Updates leverage tiers for a perp asset."], "discriminator": [110, 223, 244, 99, 34, 83, 97, 121], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpLeverageTiersInstruction"}}}]}, {"name": "UpdatePerpOpenInterestCap", "docs": ["Updates open interest cap for a perp asset."], "discriminator": [75, 137, 116, 5, 80, 123, 107, 177], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpOpenInterestCapInstruction"}}}]}, {"name": "UpdatePerpUPnlRiskFactor", "docs": ["Updates uPnL risk factor for a perp asset."], "discriminator": [113, 214, 226, 254, 147, 9, 94, 220], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpUPnlRiskFactorInstruction"}}}]}, {"name": "UpdatePerpUPnlRiskFactorForWithdrawals", "docs": ["Updates withdrawal uPnL risk factor for a perp asset."], "discriminator": [252, 32, 10, 88, 238, 245, 104, 59], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpUPnlRiskFactorForWithdrawalsInstruction"}}}]}, {"name": "UpdatePerpCancelRiskFactor", "docs": ["Updates cancel-order risk factor for a perp asset."], "discriminator": [88, 6, 185, 205, 118, 28, 129, 242], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpCancelRiskFactorInstruction"}}}]}, {"name": "UpdatePerpMarkPriceParameters", "docs": ["Updates mark price calculation parameters for a perp asset."], "discriminator": [254, 86, 13, 94, 43, 224, 160, 226], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpMarkPriceParametersInstruction"}}}]}, {"name": "UpdateTraderFees", "docs": ["Updates fee override multipliers for a trader."], "discriminator": [182, 233, 57, 205, 174, 168, 73, 237], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or fee-update delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account whose fee overrides are updated."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateTraderFeesParams"}}}]}, {"name": "UpdateMarketFees", "docs": ["Updates default fee parameters for a market orderbook."], "discriminator": [187, 36, 121, 171, 131, 19, 243, 117], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbookAccount", "writable": true, "docs": ["Orderbook account whose default fee parameters are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateMarketFeesParams"}}}]}, {"name": "UpdateWithdrawRateLimits", "docs": ["Updates withdrawal throttle budget and replenishment parameters."], "discriminator": [187, 246, 146, 182, 141, 216, 36, 1], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority or withdrawal-suspension delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "withdrawQueueAccount", "writable": true, "docs": ["Withdraw queue account whose throttle settings are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateWithdrawRateLimitsParams"}}}]}, {"name": "UpdateWithdrawParameters", "docs": ["Updates withdraw cooldown and fee parameters."], "discriminator": [87, 209, 225, 132, 91, 151, 249, 132], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "withdrawQueueAccount", "writable": true, "docs": ["Withdraw queue account whose fee parameters are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateWithdrawParametersParams"}}}]}, {"name": "UpdateAuthorities", "docs": ["Updates subordinate authorities stored in global configuration."], "discriminator": [175, 228, 137, 18, 175, 70, 220, 165], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority or permitted delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateAuthoritiesParams"}}}]}, {"name": "InitializeArena", "docs": ["Initializes a new global trader index or active trader buffer arena account."], "discriminator": [11, 37, 221, 1, 205, 120, 25, 230], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority or arena-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "headerAccount", "writable": true, "docs": ["Arena family header account."]}, {"name": "newArenaAccount", "writable": true, "docs": ["New arena account to create and initialize."]}, {"name": "systemProgram", "docs": ["System program for creating the arena account."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "InitializeArenaParams"}}}]}, {"name": "RemoveEmptyArena", "docs": ["Removes an empty active trader buffer arena account."], "discriminator": [155, 65, 162, 79, 211, 221, 197, 168], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority or arena-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "headerAccount", "writable": true, "docs": ["Active trader buffer header account."]}, {"name": "removedAccount", "writable": true, "docs": ["Empty arena account to remove."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "RemoveOracle", "docs": ["Removes one or more oracle public keys from the perp asset map."], "discriminator": [60, 93, 51, 197, 182, 42, 170, 26], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Oracle authority or oracle-update delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "RemoveOracleInstruction"}}}]}, {"name": "RemoveAllOracles", "docs": ["Removes all oracle public keys for a perp asset."], "discriminator": [164, 64, 91, 210, 137, 189, 135, 38], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "RemoveAllOraclesInstruction"}}}]}, {"name": "SetPermissionDelegated", "docs": ["Sets a whitelisted permission on a target permission account through delegated authority."], "discriminator": [35, 25, 57, 68, 255, 26, 161, 229], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority with permission to set delegated permissions."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "targetPermission", "writable": true, "docs": ["Permission account whose flags are updated."]}, {"name": "from", "docs": ["Authority seed of the target permission PDA."]}, {"name": "to", "docs": ["User seed of the target permission PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "SetPermissionInstruction"}}}]}, {"name": "DeactivateSpline", "docs": ["Deactivates a trader spline for a market."], "discriminator": [0, 254, 22, 100, 238, 197, 118, 181], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account for the market."]}, {"name": "marketAccount", "docs": ["Market orderbook account for the spline."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account being deactivated as a spline participant."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used while updating trader activity."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "ForceCancelStopLoss", "docs": ["Force cancels stop loss state for a trader after market closure."], "discriminator": [114, 230, 115, 170, 66, 79, 19, 172], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "docs": ["Closed market orderbook associated with the stop loss."]}, {"name": "funder", "writable": true, "docs": ["Funder account receiving reclaimed stop loss rent."]}, {"name": "traderAccount", "docs": ["Trader account that owns the stop loss."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to close."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "UpdateFundingParameters", "docs": ["Updates funding parameters for a perp asset."], "discriminator": [101, 195, 104, 104, 116, 20, 223, 188], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateFundingParametersInstruction"}}}]}, {"name": "CloseMarket", "docs": ["Closes a market and records its finalized mark price."], "discriminator": [88, 154, 248, 186, 48, 14, 123, 244], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account updated with finalized mark price."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account whose status is moved to Closed."]}], "args": []}, {"name": "TombstoneMarket", "docs": ["Tombstones a closed market after open interest, orders, and splines are cleared."], "discriminator": [90, 177, 17, 88, 85, 11, 177, 96], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "writable": true, "docs": ["Closed orderbook account to tombstone."]}, {"name": "perpAssetMap", "docs": ["Perp asset map account used to verify open interest is zero."]}, {"name": "splineCollection", "docs": ["Spline collection account used to verify no active splines remain."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": []}, {"name": "SettleClosedMarketPosition", "docs": ["Settles matching long and short trader positions in a closed market."], "discriminator": [27, 222, 221, 233, 136, 15, 254, 210], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account containing finalized mark price."]}, {"name": "orderbook", "writable": true, "docs": ["Closed orderbook account for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "longTrader", "writable": true, "docs": ["Trader account with a long position to settle."]}, {"name": "shortTrader", "writable": true, "docs": ["Trader account with a short position to settle."]}], "args": []}, {"name": "DeleteTombstonedMarket", "docs": ["Deletes a tombstoned market and reclaims orderbook and spline rent."], "discriminator": [146, 17, 252, 116, 222, 113, 52, 102], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "lamportRecipient", "writable": true, "docs": ["Account receiving reclaimed lamports."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account from which the market metadata is removed."]}, {"name": "orderbook", "writable": true, "docs": ["Tombstoned orderbook account to delete."]}, {"name": "splineCollection", "writable": true, "docs": ["Spline collection account to delete."]}], "args": []}, {"name": "UpdateSplinePositionLimitsConfig", "docs": ["Updates position-size limits and leverage decrease configuration for a spline trader."], "discriminator": [96, 60, 5, 112, 19, 144, 13, 107], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Trader or authority signer updating spline position limits."]}, {"name": "traderAccount", "docs": ["Trader account associated with the spline."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account holding the trader spline."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplinePositionLimitsConfigParams"}}}]}], "accounts": [{"name": "GlobalConfiguration", "discriminator": [37, 146, 212, 210, 47, 136, 111, 20], "docs": ["Global exchange configuration account derived from the \"global\" PDA seed.", "Stores configured authorities, canonical collateral mint, global vault, PerpAssetMap, GlobalTraderIndex, ActiveTraderBuffer, and WithdrawQueue addresses."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}]}}, {"name": "OrderbookHeader", "discriminator": [65, 1, 40, 241, 166, 14, 86, 188], "docs": ["Market orderbook account header for a perpetual market.", "Market orderbooks are explicit market accounts created during AddMarket and recorded in PerpAssetMap metadata; they are not PDAs."]}, {"name": "SplineCollectionHeader", "discriminator": [192, 245, 182, 191, 88, 37, 79, 48], "docs": ["Spline collection account header derived from [\"spline\", orderbook].", "Stores spline market-making state for one market."], "pda": {"seeds": [{"kind": "const", "value": [115, 112, 108, 105, 110, 101]}, {"kind": "account", "path": "marketAccount", "account": "OrderbookHeader"}]}}, {"name": "Trader", "discriminator": [41, 97, 73, 105, 110, 214, 112, 9], "docs": ["Trader account derived from [\"trader\", authority, trader_pda_index, trader_subaccount_index].", "Stores collateral, positions, open orders, and per-trader risk state."], "pda": {"seeds": [{"kind": "const", "value": [116, 114, 97, 100, 101, 114]}, {"kind": "account", "path": "authority"}, {"kind": "arg", "path": "traderPdaIndex"}, {"kind": "arg", "path": "traderSubaccountIndex"}]}}, {"name": "ActiveTraderBufferHeader", "discriminator": [192, 255, 205, 165, 80, 154, 131, 5], "docs": ["Header arena for the active trader buffer derived from [\"active_trader_buffer\", 0u16]."], "pda": {"seeds": [{"kind": "const", "value": [97, 99, 116, 105, 118, 101, 95, 116, 114, 97, 100, 101, 114, 95, 98, 117, 102, 102, 101, 114]}, {"kind": "const", "value": [0, 0]}]}}, {"name": "ActiveTraderBufferArenaHeader", "discriminator": [6, 249, 57, 137, 82, 121, 191, 107], "docs": ["Additional active trader buffer arena derived from [\"active_trader_buffer\", arena_index]."], "pda": {"seeds": [{"kind": "const", "value": [97, 99, 116, 105, 118, 101, 95, 116, 114, 97, 100, 101, 114, 95, 98, 117, 102, 102, 101, 114]}, {"kind": "arg", "path": "arenaIndex"}]}}, {"name": "GlobalTraderIndexHeader", "discriminator": [145, 92, 169, 6, 5, 144, 1, 205], "docs": ["Header arena for the global trader index derived from [\"global_trader_index\", 0u16]."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108, 95, 116, 114, 97, 100, 101, 114, 95, 105, 110, 100, 101, 120]}, {"kind": "const", "value": [0, 0]}]}}, {"name": "GlobalTraderIndexArenaHeader", "discriminator": [98, 110, 179, 71, 203, 120, 200, 235], "docs": ["Additional global trader index arena derived from [\"global_trader_index\", arena_index]."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108, 95, 116, 114, 97, 100, 101, 114, 95, 105, 110, 100, 101, 120]}, {"kind": "arg", "path": "arenaIndex"}]}}, {"name": "PerpAssetMap", "discriminator": [142, 54, 115, 223, 124, 239, 108, 38], "docs": ["Perpetual asset map account storing market metadata, risk parameters, oracle metadata, and funding state.", "The account pubkey is stored in GlobalConfiguration rather than derived from a fixed PDA seed; it is not a PDA."]}, {"name": "WithdrawQueueHeader", "discriminator": [125, 59, 94, 108, 171, 16, 43, 228], "docs": ["Withdraw queue account storing pending withdrawals and withdrawal throttle state.", "The account pubkey is stored in GlobalConfiguration rather than derived from a fixed PDA seed; it is not a PDA."]}, {"name": "PermissionAccount", "discriminator": [180, 172, 109, 215, 129, 165, 97, 38], "docs": ["Permission delegation account derived from [\"permission\", authority, user].", "Stores delegated capability bits and optional expiry/usage constraints."], "pda": {"seeds": [{"kind": "const", "value": [112, 101, 114, 109, 105, 115, 115, 105, 111, 110]}, {"kind": "account", "path": "authority"}, {"kind": "account", "path": "user"}]}}, {"name": "StopLosses", "discriminator": [127, 64, 139, 143, 70, 43, 167, 152], "docs": ["Stop-loss account derived from [\"stoploss\", trader_account, asset_id].", "Stores trigger orders for one trader and market."], "pda": {"seeds": [{"kind": "const", "value": [115, 116, 111, 112, 108, 111, 115, 115]}, {"kind": "account", "path": "traderAccount", "account": "Trader"}, {"kind": "arg", "path": "assetId"}]}}, {"name": "EscrowHeader", "discriminator": [50, 4, 48, 59, 209, 138, 251, 204], "docs": ["Escrow inbox account derived from [\"escrow\", authority].", "Stores pending escrow requests for the authority."], "pda": {"seeds": [{"kind": "const", "value": [101, 115, 99, 114, 111, 119]}, {"kind": "account", "path": "authority"}]}}, {"name": "ConditionalOrderHeader", "discriminator": [147, 116, 229, 177, 249, 200, 146, 49], "docs": ["Conditional orders account derived from [\"conditional_orders\", trader_account].", "Stores attached and position-scoped conditional orders for a trader account."], "pda": {"seeds": [{"kind": "const", "value": [99, 111, 110, 100, 105, 116, 105, 111, 110, 97, 108, 95, 111, 114, 100, 101, 114, 115]}, {"kind": "account", "path": "traderAccount", "account": "Trader"}]}}, {"name": "DirectMarkOverride", "discriminator": [142, 54, 115, 223, 124, 239, 108, 38]}, {"name": "ReferencePriceOverride", "discriminator": [210, 123, 24, 82, 132, 17, 53, 94]}], "types": [{"name": "OrderPacket", "docs": ["Serialized parameters for Phoenix order placement instructions."], "type": {"kind": "struct", "fields": [{"name": "kind", "type": {"defined": {"name": "OrderPacketKind"}}}]}}, {"name": "OrderPacketKind", "docs": ["Supported order variants accepted by the Phoenix matching engine."], "type": {"kind": "enum", "variants": [{"name": "PostOnly", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "slide", "type": "bool"}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}, {"name": "Limit", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "selfTradeBehavior", "type": {"defined": {"name": "SelfTradeBehavior"}}}, {"name": "matchLimit", "type": {"option": "u64"}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}, {"name": "ImmediateOrCancel", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "numQuoteLots", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "minBaseLotsToFill", "type": {"defined": {"name": "BaseLots"}}}, {"name": "minQuoteLotsToFill", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "selfTradeBehavior", "type": {"defined": {"name": "SelfTradeBehavior"}}}, {"name": "matchLimit", "type": {"option": "u64"}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}]}}, {"name": "Side", "docs": ["Trading direction for an order."], "type": {"kind": "enum", "variants": [{"name": "Bid"}, {"name": "Ask"}]}}, {"name": "SelfTradeBehavior", "docs": ["Policy for handling matches against the submitting trader's resting orders."], "type": {"kind": "enum", "variants": [{"name": "Abort"}, {"name": "CancelProvide"}, {"name": "DecrementTake"}]}}, {"name": "BaseLots", "docs": ["Quantity wrapper representing discrete base lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "QuoteLots", "docs": ["Quantity wrapper representing discrete quote lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "Ticks", "docs": ["Discrete price unit measured in quote lots per base lot."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "BaseLotsPerTick", "docs": ["Density of liquidity per tick for spline market making."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OrderFlags", "docs": ["Bitmask flags applied to an order (currently only the reduce-only bit is used)."], "type": {"kind": "struct", "fields": [{"name": "flags", "type": "u8"}]}}, {"name": "OrderIds", "docs": ["Collection of cancel identifiers used to target specific resting orders."], "type": {"kind": "struct", "fields": [{"name": "orderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}]}}, {"name": "CancelUpToInstruction", "docs": ["Parameters controlling cancel-up-to behaviour."], "type": {"kind": "struct", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "numOrdersToCancel", "type": {"option": "u64"}}, {"name": "tickLimit", "type": {"option": "u64"}}]}}, {"name": "CancelId", "docs": ["Identifies a resting order along with an optional hint into the orderbook."], "type": {"kind": "struct", "fields": [{"name": "nodePointer", "type": {"defined": {"name": "NodePointer"}}}, {"name": "orderId", "type": {"defined": {"name": "FIFOOrderId"}}}]}}, {"name": "FIFOOrderId", "docs": ["Stable identifier for a resting order composed of price ticks and sequence number."], "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "NodePointer", "docs": ["Pointer hint into the orderbook linked list; zero represents a null pointer."], "type": {"kind": "struct", "fields": [{"name": "value", "type": "u32"}]}}, {"name": "Symbol", "docs": ["Fixed-width (16 byte) asset symbol stored as UTF-8 with trailing zero padding."], "type": {"kind": "struct", "fields": [{"name": "symbolBytes", "type": {"array": ["u8", 16]}}]}}, {"name": "Price", "docs": ["Price representation composed of a mantissa (`value`) and decimal exponent (`expo`)."], "type": {"kind": "struct", "fields": [{"name": "value", "type": "u64"}, {"name": "expo", "type": "u8"}]}}, {"name": "OraclePriceUpdate", "docs": ["Single oracle price update targeting a specific perpetual asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetId", "type": {"defined": {"name": "Symbol"}}}, {"name": "newExchangePerpPrice", "type": {"option": {"defined": {"name": "Price"}}}}, {"name": "newExchangeSpotPrice", "type": {"defined": {"name": "Price"}}}]}}, {"name": "UpdateOraclePricesWithOrderingInstruction", "docs": ["Oracle instruction payload containing ordered price updates and timestamp reset policy."], "type": {"kind": "struct", "fields": [{"name": "updateTimestamp", "type": "u64"}, {"name": "updates", "type": {"vec": {"defined": {"name": "OraclePriceUpdate"}}}}, {"name": "shouldResetTimestamp", "type": "bool"}]}}, {"name": "UpdateSplinePriceParams", "docs": ["Parameters for updating a spline's mid-price."], "type": {"kind": "struct", "fields": [{"name": "newMidPrice", "type": "u64"}, {"name": "userUpdateSlot", "type": {"option": "u64"}}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "UpdateSplinePriceParamsWithOrdering", "docs": ["Parameters for updating a spline's mid-price with anti-reordering protection."], "type": {"kind": "struct", "fields": [{"name": "newMidPrice", "type": "u64"}, {"name": "userUpdateSlot", "type": {"option": "u64"}}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "overrideSequenceNumber", "type": "bool"}]}}, {"name": "DepositFundsInstruction", "docs": ["Parameters for depositing collateral into the global vault."], "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "RegisterTraderParams", "docs": ["Parameters for registering a new trader account."], "type": {"kind": "struct", "fields": [{"name": "maxPositions", "type": "u64"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}]}}, {"name": "LiquidationParams", "docs": ["Parameters for liquidating a trader's position via market order."], "type": {"kind": "struct", "fields": [{"name": "assetMint", "type": "pubkey"}, {"name": "liquidationSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "liquidationPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "fillOrKill", "type": "bool"}]}}, {"name": "TransferRequest", "docs": ["Transfer payload describing how many base lots of an asset should move from the liquidatee to the liquidator."], "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u64"}, {"name": "amount", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "LiquidationTransferParams", "docs": ["Parameters controlling liquidation transfers between traders."], "type": {"kind": "struct", "fields": [{"name": "transferRequests", "type": {"vec": {"defined": {"name": "TransferRequest"}}}}, {"name": "executeAllTransfers", "type": "bool"}]}}, {"name": "ClosePositionsParams", "docs": ["Parameters for closing matched positions between at-loss and in-profit traders."], "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u64"}, {"name": "amount", "type": {"defined": {"name": "BaseLots"}}}, {"name": "atLossCloseValue", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "inProfitCloseValue", "type": {"option": {"defined": {"name": "QuoteLots"}}}}]}}, {"name": "MultipleOrderPacket", "docs": ["Batch order packet for placing multiple post-only limit orders in a single transaction."], "type": {"kind": "struct", "fields": [{"name": "bids", "type": {"vec": {"defined": {"name": "CondensedOrder"}}}}, {"name": "asks", "type": {"vec": {"defined": {"name": "CondensedOrder"}}}}, {"name": "clientOrderId", "type": {"option": {"array": ["u8", 16]}}}, {"name": "slide", "type": "bool"}]}}, {"name": "CondensedOrder", "docs": ["Condensed order representation for batch order placement (price, size, and optional TiF)."], "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": "u64"}, {"name": "sizeInBaseLots", "type": "u64"}, {"name": "lastValidSlot", "type": {"option": "u64"}}]}}, {"name": "WithdrawFundsInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64", "docs": ["Amount in quote lots to withdraw."]}]}}, {"name": "UpdateSplineParametersParams", "type": {"kind": "struct", "fields": [{"name": "bidRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "askRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "UpdateSplineParametersParamsWithOrdering", "type": {"kind": "struct", "fields": [{"name": "bidRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "askRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "overrideSequenceNumber", "type": "bool"}]}}, {"name": "TickRegionParams", "type": {"kind": "struct", "fields": [{"name": "startOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "endOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "density", "type": {"defined": {"name": "BaseLotsPerTick"}}}, {"name": "lifespan", "type": "u64"}]}}, {"name": "PlaceStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "triggerPrice", "type": "u64"}, {"name": "executionPrice", "type": "u64"}, {"name": "tradeSize", "type": "u64"}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}]}}, {"name": "ExecuteStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "CancelStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "ForceCancelRiskIncreasingParams", "type": {"kind": "struct", "fields": []}}, {"name": "Direction", "type": {"kind": "enum", "variants": [{"name": "GreaterThan"}, {"name": "LessThan"}]}}, {"name": "StopLossOrderKind", "type": {"kind": "enum", "variants": [{"name": "IOC"}, {"name": "Limit"}]}}, {"name": "LogHeader", "type": {"kind": "struct", "fields": [{"name": "logBatchIndex", "docs": ["Index of this log batch for tracking event sequences."], "type": "u32"}, {"name": "totalEvents", "docs": ["Number of events serialized in this log batch."], "type": "u32"}]}}, {"name": "OrderbookHeader", "docs": ["Fixed metadata prefix for a Phoenix Eternal orderbook account.", "The large recent-trade buffer and dynamic orderbook bytes follow this prefix and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "marketStatus", "type": "u8"}, {"name": "baseLotsDecimals", "type": "i8"}, {"name": "padding0", "type": {"array": ["u8", 6]}}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "assetId", "type": "u32"}, {"name": "assetIdPadding", "type": {"array": ["u8", 4]}}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "tickSizeInQuoteLotsPerBaseLot", "type": "u64"}, {"name": "orderSequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "tradeSequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "defaultTakerFeeMicro", "type": "u32"}, {"name": "defaultMakerFeeMicro", "type": "i32"}, {"name": "totalMakerQuoteLotFees", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "totalTakerQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}]}}, {"name": "SplineCollectionHeader", "docs": ["Fixed SplineCollectionHeader prefix for a Phoenix Eternal spline collection account.", "Dynamic spline map bytes follow this header and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "market", "type": "pubkey"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "numSplines", "type": "u32"}, {"name": "numActive", "type": "u32"}, {"name": "padding", "type": {"array": ["u8", 32]}}]}}, {"name": "TraderState", "docs": ["Fixed trader state embedded in the Phoenix Eternal DynamicTraderHeader."], "type": {"kind": "struct", "fields": [{"name": "quoteLotCollateral", "type": "i64"}, {"name": "flags", "type": "u32"}, {"name": "padding", "type": {"array": ["u8", 1]}}, {"name": "globalPositionSequenceNumber", "type": "u8"}, {"name": "makerFeeOverrideMultiplier", "type": "i8"}, {"name": "takerFeeOverrideMultiplier", "type": "i8"}]}}, {"name": "Trader", "docs": ["Fixed DynamicTraderHeader prefix for a Phoenix Eternal trader account.", "Dynamic position map bytes follow this header and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "key", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "traderState", "type": {"defined": {"name": "TraderState"}}}, {"name": "padding0", "type": {"array": ["u8", 4]}}, {"name": "withdrawQueueNode", "type": "u32"}, {"name": "maxPositions", "type": "u64"}, {"name": "positionAuthority", "type": "pubkey"}, {"name": "numMarketsWithSplines", "type": "u16"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "padding1", "type": {"array": ["u8", 4]}}, {"name": "lastDepositSlot", "type": "u64"}, {"name": "conditionalOrderBits", "type": {"array": ["u8", 24]}}]}}, {"name": "ActiveTraderBufferHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full active trader buffer layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "ActiveTraderBufferArenaHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full active trader buffer arena layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalTraderIndexHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full global trader index layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalTraderIndexArenaHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full global trader index arena layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalConfiguration", "type": {"kind": "struct", "fields": [{"name": "accountKey", "type": "pubkey"}, {"name": "rootAuthority", "type": "pubkey"}, {"name": "riskAuthority", "type": "pubkey"}, {"name": "marketAuthority", "type": "pubkey"}, {"name": "oracleAuthority", "type": "pubkey"}, {"name": "successorAuthority", "type": "pubkey"}, {"name": "adlAuthority", "type": "pubkey"}, {"name": "cancelAuthority", "type": "pubkey"}, {"name": "backstopAuthority", "type": "pubkey"}, {"name": "canonicalTokenMintKey", "type": "pubkey"}, {"name": "globalVaultKey", "type": "pubkey"}, {"name": "perpAssetMapKey", "type": "pubkey"}, {"name": "globalTraderIndexHeaderKey", "type": "pubkey"}, {"name": "activeTraderBufferHeaderKey", "type": "pubkey"}, {"name": "totalQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "unclaimedQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "withdrawQueueKey", "type": "pubkey"}, {"name": "exchangeStatus", "type": "u8"}, {"name": "quoteDecimals", "type": "u8"}, {"name": "withdrawalMarginFactorBps", "type": "u16"}, {"name": "padding0", "type": {"array": ["u8", 4]}}, {"name": "padding1", "type": {"array": ["u64", 32]}}, {"name": "padding2", "type": {"array": ["u64", 32]}}, {"name": "padding3", "type": {"array": ["u64", 32]}}, {"name": "padding4", "type": {"array": ["u64", 32]}}, {"name": "padding5", "type": {"array": ["u64", 32]}}, {"name": "padding6", "type": {"array": ["u64", 32]}}, {"name": "padding7", "type": {"array": ["u64", 32]}}, {"name": "padding8", "type": {"array": ["u64", 32]}}]}}, {"name": "SequenceNumber", "docs": ["Pair of sequence number and last update slot."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "lastUpdateSlot", "type": "u64"}]}}, {"name": "PerpAssetMetadata", "docs": ["Opaque fixed-size zero-copy PerpAssetMetadata payload stored inside PerpAssetMap."], "type": {"kind": "struct", "fields": [{"name": "raw", "type": {"array": ["u8", 1568]}}]}}, {"name": "PerpAssetMapEntry", "docs": ["One fixed slot in the PerpAssetMap ShortMapV2 backing array."], "type": {"kind": "struct", "fields": [{"name": "key", "type": {"defined": {"name": "Symbol"}}}, {"name": "value", "type": {"defined": {"name": "PerpAssetMetadata"}}}]}}, {"name": "PerpAssetMetadataShortMapV2", "docs": ["Zero-copy ShortMapV2 layout."], "type": {"kind": "struct", "fields": [{"name": "slotsUsed", "type": "u32"}, {"name": "tombstones", "type": "u32"}, {"name": "capacity", "type": "u64"}, {"name": "data", "type": {"array": [{"defined": {"name": "PerpAssetMapEntry"}}, 1024]}}]}}, {"name": "PerpAssetMap", "docs": ["Canonical Eternal perpetual asset map account."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "numAssets", "type": "u16"}, {"name": "padding0", "type": {"array": ["u8", 6]}}, {"name": "metadatas", "type": {"defined": {"name": "PerpAssetMetadataShortMapV2"}}}]}}, {"name": "WithdrawThrottle", "docs": ["Fixed WithdrawThrottle payload embedded in WithdrawQueueHeader."], "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "remainingBudget", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "replenishAmountPerSlot", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "lastUpdateSlot", "type": "u64"}]}}, {"name": "WithdrawQueueHeader", "docs": ["Fixed WithdrawQueueHeader prefix for the Phoenix Eternal withdraw queue account.", "The deque of pending withdrawal requests follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "withdrawThrottle", "type": {"defined": {"name": "WithdrawThrottle"}}}, {"name": "totalQueuedAmount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "withdrawalFee", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "enqueueingFee", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "reserved", "type": {"array": ["u64", 5]}}]}}, {"name": "PermissionAccount", "docs": ["Fixed Phoenix Eternal permission delegation account layout."], "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "user", "type": "pubkey"}, {"name": "bump", "type": "u8"}, {"name": "padding1", "type": {"array": ["u8", 7]}}, {"name": "permission", "type": "u64"}, {"name": "expiresAtTimestamp", "type": "i64"}, {"name": "numSignerActionsRemaining", "type": "i64"}, {"name": "padding2", "type": {"array": ["u8", 64]}}]}}, {"name": "StopLoss", "docs": ["One fixed trigger order slot stored inside the StopLosses account."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "slot", "type": "u64"}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "flags", "type": "u8"}, {"name": "padding", "type": {"array": ["u8", 6]}}, {"name": "padding2", "type": {"array": ["u8", 32]}}]}}, {"name": "StopLosses", "docs": ["Fixed Phoenix Eternal StopLosses account layout."], "type": {"kind": "struct", "fields": [{"name": "stopLosses", "type": {"array": [{"defined": {"name": "StopLoss"}}, 2]}}, {"name": "initialized", "type": "u64"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "fundingKey", "type": "pubkey"}, {"name": "traderKey", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "assetIdPadding", "type": {"array": ["u8", 4]}}, {"name": "padding", "type": {"array": ["u64", 8]}}]}}, {"name": "EscrowHeader", "docs": ["Fixed EscrowHeader prefix for a Phoenix Eternal escrow account.", "The dynamic escrow request list follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "funderKey", "type": "pubkey"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "len", "type": "u64"}, {"name": "capacity", "type": "u64"}]}}, {"name": "ConditionalOrderHeader", "docs": ["Fixed ConditionalOrderHeader prefix for a Phoenix Eternal conditional orders account.", "The dynamic conditional order array follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "traderKey", "type": "pubkey"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "len", "type": "u8"}, {"name": "capacity", "type": "u8"}, {"name": "padding", "type": {"array": ["u8", 6]}}]}}, {"name": "RevokePermissionParams", "type": {"kind": "struct", "fields": [{"name": "permissionFlagsToRevoke", "type": "u64"}]}}, {"name": "SetPermissionInstruction", "type": {"kind": "struct", "fields": [{"name": "permission", "type": "u64"}, {"name": "expiresAtTimestamp", "type": {"option": "i64"}}, {"name": "allowedSignerActions", "type": {"option": "i64"}}]}}, {"name": "TraderCapabilityToggleTarget", "type": {"kind": "enum", "variants": [{"name": "PlaceLimitOrder"}, {"name": "PlaceMarketOrder"}, {"name": "RiskIncreasingTrade"}, {"name": "RiskReducingTrade"}, {"name": "DepositCollateral"}, {"name": "WithdrawCollateral"}]}}, {"name": "TraderCapabilityToggle", "type": {"kind": "struct", "fields": [{"name": "target", "type": {"defined": {"name": "TraderCapabilityToggleTarget"}}}, {"name": "enable", "type": "bool"}]}}, {"name": "TraderCapabilityUpdate", "type": {"kind": "struct", "fields": [{"name": "toggles", "type": {"vec": {"defined": {"name": "TraderCapabilityToggle"}}}}]}}, {"name": "TransferCollateralInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "TransferCollateralChildToParentInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "UncrossCrankParams", "type": {"kind": "struct", "fields": [{"name": "matchLimit", "type": "u64"}]}}, {"name": "ClearExpiredOrdersParams", "type": {"kind": "struct", "fields": [{"name": "maxOrdersPerSide", "type": {"option": "u64"}}]}}, {"name": "TriggerOrderParams", "docs": ["Trigger and execution parameters for one half of a conditional order."], "type": {"kind": "struct", "fields": [{"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}, {"name": "triggerPrice", "type": "u64"}, {"name": "executionPrice", "type": "u64"}]}}, {"name": "CreateConditionalOrdersAccountInstruction", "type": {"kind": "struct", "fields": [{"name": "capacity", "type": "u8"}]}}, {"name": "PlacePositionConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u32"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "sizeBaseLots", "type": {"option": "u64"}}, {"name": "sizePercent", "type": {"option": "u8"}}]}}, {"name": "CancelConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "index", "type": "u8"}, {"name": "disableFirst", "type": "bool"}, {"name": "disableSecond", "type": "bool"}]}}, {"name": "PlaceAttachedConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "orderId", "type": {"defined": {"name": "FIFOOrderId"}}}, {"name": "assetId", "type": "u32"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}]}}, {"name": "PlaceLimitOrderWithConditionalsInstruction", "type": {"kind": "struct", "fields": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}, {"name": "slot", "type": "u64"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}]}}, {"name": "ExecuteConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "index", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "OffChainMarketEvent", "docs": ["Borsh payload of the Log instruction after the 8-byte instruction discriminator."], "type": {"kind": "struct", "fields": [{"name": "batchIndex", "type": "u32"}, {"name": "events", "type": {"vec": {"defined": {"name": "MarketEvent"}}}}]}}, {"name": "OffChainMarketEventLengths", "docs": ["Borsh payload of the LogEventLengths instruction after the 8-byte instruction discriminator."], "type": {"kind": "struct", "fields": [{"name": "batchIndex", "type": "u32"}, {"name": "lengths", "type": {"vec": "u16"}}]}}, {"name": "SignedBaseLots", "docs": ["Signed quantity wrapper representing discrete base lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedQuoteLots", "docs": ["Signed quantity wrapper representing discrete quote lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedFeeRateMicro", "docs": ["Signed fee rate in millionths."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i32"}]}}, {"name": "SignedQuoteLotsPerBaseLot", "docs": ["Signed quote lots per base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedQuoteLotsPerBaseLotUpcasted", "docs": ["Upcasted signed quote lots per base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i128"}]}}, {"name": "SignedTicks", "docs": ["Signed discrete tick offset."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "FundingRateUnitInSeconds", "docs": ["Funding time unit measured in seconds."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "BaseLotsU32", "docs": ["u32-backed base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u32"}]}}, {"name": "BaseLotsPerTickU32", "docs": ["u32-backed spline liquidity density wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u32"}]}}, {"name": "BasisPoints", "docs": ["Basis points wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "Constant", "docs": ["Dimensionless constant wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OptionalNonZeroU64", "docs": ["Zero represents None; non-zero represents Some(value)."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OptionalFIFOOrderId", "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "TraderCapabilityFlags", "type": {"kind": "struct", "fields": [{"name": "bits", "type": "u32"}]}}, {"name": "MarketStatus", "type": {"kind": "enum", "variants": [{"name": "Uninitialized"}, {"name": "Active"}, {"name": "PostOnly"}, {"name": "Paused"}, {"name": "Closed"}, {"name": "Tombstoned"}]}}, {"name": "CommodityMarketState", "type": {"kind": "enum", "variants": [{"name": "Active"}, {"name": "AfterHours"}, {"name": "Reopen"}]}}, {"name": "AuthorityType", "type": {"kind": "enum", "variants": [{"name": "Root"}, {"name": "Risk"}, {"name": "Market"}, {"name": "Oracle"}, {"name": "Cancel"}, {"name": "Backstop"}, {"name": "ADL"}]}}, {"name": "ValidationRule", "type": {"kind": "enum", "variants": [{"name": "Ignore"}, {"name": "Require"}, {"name": "Forbid"}]}}, {"name": "LeverageTier", "type": {"kind": "struct", "fields": [{"name": "upperBoundSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "maxLeverage", "type": {"defined": {"name": "Constant"}}}, {"name": "limitOrderRiskFactor", "type": {"defined": {"name": "BasisPoints"}}}]}}, {"name": "LeverageTiers", "type": {"kind": "struct", "fields": [{"name": "tiers", "type": {"array": [{"defined": {"name": "LeverageTier"}}, 4]}}]}}, {"name": "PositionSizeLimits", "type": {"kind": "struct", "fields": [{"name": "long", "type": "u32"}, {"name": "short", "type": "u32"}]}}, {"name": "PositionSizeLimit", "type": {"kind": "enum", "variants": [{"name": "Disabled"}, {"name": "Limit", "fields": [{"name": "value", "type": {"defined": {"name": "PositionSizeLimits"}}}]}]}}, {"name": "TickRegion", "type": {"kind": "struct", "fields": [{"name": "startOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "endOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "density", "type": {"defined": {"name": "BaseLotsPerTickU32"}}}, {"name": "topLevelHiddenTakeSize", "type": {"defined": {"name": "BaseLotsPerTickU32"}}}, {"name": "totalSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLotsU32"}}}, {"name": "hiddenFilledSize", "type": {"defined": {"name": "BaseLotsU32"}}}, {"name": "lifespan", "type": "u64"}]}}, {"name": "OrderRejectionReason", "type": {"kind": "enum", "variants": [{"name": "TooManyLimitOrders"}, {"name": "PostOnlyCross"}, {"name": "InvalidOrderPacket"}, {"name": "TiFInvalid"}, {"name": "OutsideExecutionPriceBand"}]}}, {"name": "OrderResidualDiscardReason", "type": {"kind": "enum", "variants": [{"name": "OutsideExecutionPriceBand"}]}}, {"name": "OrderModificationReason", "type": {"kind": "enum", "variants": [{"name": "UserRequested"}, {"name": "AuthorityForced"}, {"name": "SelfTradeCancelProvide"}, {"name": "SelfTradeDecrementTake"}, {"name": "Expired"}, {"name": "ReduceOnlyInvalidated"}, {"name": "RiskCapacityExceeded"}, {"name": "BookCapacityEvicted"}, {"name": "Tombstone"}, {"name": "TopOfBookOutOfBounds"}, {"name": "StopLossCascade"}]}}, {"name": "ConditionalOrderPingStateSnapshot", "type": {"kind": "struct", "fields": [{"name": "value", "type": "u8"}]}}, {"name": "ConditionalOrderPingSnapshot", "type": {"kind": "struct", "fields": [{"name": "state", "type": {"defined": {"name": "ConditionalOrderPingStateSnapshot"}}}, {"name": "attachedOrderId", "type": {"defined": {"name": "OptionalFIFOOrderId"}}}, {"name": "maxSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "fillableSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "coPositionSequenceNumber", "type": {"option": "u8"}}]}}, {"name": "MarkPriceConfig", "type": {"kind": "struct", "fields": [{"name": "emaPeriodSlots", "type": "u64"}, {"name": "emaDiffRadius", "type": "u64"}, {"name": "bookPriceRadius", "type": "u64"}, {"name": "commoditiesAfterHoursRadius", "type": "u64"}, {"name": "spotPriceWeight", "type": "u64"}, {"name": "bookPriceWeight", "type": "u64"}, {"name": "perpPriceWeight", "type": "u64"}, {"name": "spotPriceStaleThreshold", "type": "u64"}, {"name": "bookPriceStaleThreshold", "type": "u64"}, {"name": "perpPriceStaleThreshold", "type": "u64"}, {"name": "riskActionPriceValidityRules", "type": {"array": [{"array": [{"array": [{"defined": {"name": "ValidationRule"}}, 8]}, 4]}, 8]}}, {"name": "oracleDivergenceRadius", "type": "u16"}, {"name": "minOracleResponses", "type": "u8"}, {"name": "commoditiesAfterHoursRadiusBps", "type": "u64"}]}}, {"name": "WithdrawConfig", "type": {"kind": "struct", "fields": [{"name": "depositCooldownPeriodInSlots", "type": "u64"}, {"name": "withdrawalFee", "type": "u64"}, {"name": "enqueueingFee", "type": "u64"}]}}, {"name": "WithdrawRateLimitConfig", "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": "u64"}, {"name": "replenishAmountPerSlot", "type": "u64"}]}}, {"name": "FundingConfig", "type": {"kind": "struct", "fields": [{"name": "fundingIntervalSeconds", "type": "u64"}, {"name": "fundingPeriodSeconds", "type": "u64"}, {"name": "maxFundingRate", "type": "i64"}]}}, {"name": "CommodityMarketStateConfig", "type": {"kind": "struct", "fields": [{"name": "marketState", "type": {"defined": {"name": "CommodityMarketState"}}}, {"name": "isCommodity", "type": "bool"}, {"name": "lastKnownIndexPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "lastIndexExpiryTimestamp", "type": "u64"}, {"name": "commoditiesAfterHoursRadius", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "MarketFeeConfig", "type": {"kind": "struct", "fields": [{"name": "defaultTakerFeeMicro", "type": "u32"}, {"name": "defaultMakerFeeMicro", "type": "i32"}]}}, {"name": "AdminParameterUpdateKind", "type": {"kind": "enum", "variants": [{"name": "CancelRiskFactor", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "IsolatedOnly", "kind": "struct", "fields": [{"name": "previous", "type": "bool"}, {"name": "new", "type": "bool"}]}, {"name": "LeverageTiers", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "LeverageTiers"}}}, {"name": "new", "type": {"defined": {"name": "LeverageTiers"}}}]}, {"name": "MarkPriceParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "MarkPriceConfig"}}}, {"name": "new", "type": {"defined": {"name": "MarkPriceConfig"}}}]}, {"name": "OpenInterestCap", "kind": "struct", "fields": [{"name": "previous", "type": "u64"}, {"name": "new", "type": "u64"}]}, {"name": "UpnlRiskFactor", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "UpnlRiskFactorForWithdrawals", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "WithdrawParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "WithdrawConfig"}}}, {"name": "new", "type": {"defined": {"name": "WithdrawConfig"}}}]}, {"name": "WithdrawRateLimits", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "WithdrawRateLimitConfig"}}}, {"name": "new", "type": {"defined": {"name": "WithdrawRateLimitConfig"}}}]}, {"name": "TraderCapability", "kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "previousFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}]}, {"name": "FundingParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "FundingConfig"}}}, {"name": "new", "type": {"defined": {"name": "FundingConfig"}}}]}, {"name": "MarketFees", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "MarketFeeConfig"}}}, {"name": "new", "type": {"defined": {"name": "MarketFeeConfig"}}}]}, {"name": "OpenInterestAdjustment", "kind": "struct", "fields": [{"name": "previousOpenInterest", "type": "u64"}, {"name": "newOpenInterest", "type": "u64"}]}, {"name": "CommodityMarketState", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "CommodityMarketStateConfig"}}}, {"name": "new", "type": {"defined": {"name": "CommodityMarketStateConfig"}}}]}]}}, {"name": "EscrowAction", "type": {"kind": "enum", "variants": [{"name": "Noop", "kind": "struct", "fields": [{"name": "padding0", "type": {"array": ["u8", 8]}}, {"name": "padding1", "type": {"array": ["u8", 128]}}]}, {"name": "Cash", "kind": "struct", "fields": [{"name": "amount", "type": "u64"}, {"name": "padding0", "type": {"array": ["u8", 128]}}]}]}}, {"name": "EscrowRequestCancelReason", "type": {"kind": "enum", "variants": [{"name": "Expiration"}, {"name": "CancelledBySender"}, {"name": "CancelledByReceiver"}]}}, {"name": "MarketEvent", "docs": ["Borsh enum emitted by Eternal Log instructions. Variant discriminators are one-byte ordinals."], "type": {"kind": "enum", "variants": [{"name": "SlotContext", "fields": [{"name": "value", "type": {"defined": {"name": "SlotContextEvent"}}}]}, {"name": "Header", "fields": [{"name": "value", "type": {"defined": {"name": "MarketEventHeader"}}}]}, {"name": "OrderPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "OrderPlacedEvent"}}}]}, {"name": "OrderFilled", "fields": [{"name": "value", "type": {"defined": {"name": "OrderFilledEvent"}}}]}, {"name": "OrderRejected", "fields": [{"name": "value", "type": {"defined": {"name": "OrderRejectedEvent"}}}]}, {"name": "SplineFilled", "fields": [{"name": "value", "type": {"defined": {"name": "SplineFilledEvent"}}}]}, {"name": "TradeSummary", "fields": [{"name": "value", "type": {"defined": {"name": "TradeSummaryEvent"}}}]}, {"name": "OrderModified", "fields": [{"name": "value", "type": {"defined": {"name": "OrderModifiedEvent"}}}]}, {"name": "MarketSummary", "fields": [{"name": "value", "type": {"defined": {"name": "MarketSummaryEvent"}}}]}, {"name": "TraderRegistered", "fields": [{"name": "value", "type": {"defined": {"name": "TraderRegisteredEvent"}}}]}, {"name": "TraderCollateralTransferred", "fields": [{"name": "value", "type": {"defined": {"name": "TraderCollateralTransferredEvent"}}}]}, {"name": "TraderActivated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderActivatedEvent"}}}]}, {"name": "TraderDeactivated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderDeactivatedEvent"}}}]}, {"name": "TraderFundsDeposited", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsDepositedEvent"}}}]}, {"name": "TraderFundsWithdrawn", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderFundsWithdrawnEnqueued", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderFundsWithdrawnDropped", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderWithdrawCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderWithdrawCancelledEvent"}}}]}, {"name": "TraderFundingSettled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundingSettledEvent"}}}]}, {"name": "SplineRegistered", "fields": [{"name": "value", "type": {"defined": {"name": "SplineRegisteredEvent"}}}]}, {"name": "SplineActivated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineActivatedEvent"}}}]}, {"name": "SplineDeactivated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineDeactivatedEvent"}}}]}, {"name": "SplinePriceUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePriceUpdatedEvent"}}}]}, {"name": "SplineParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineParametersUpdatedEvent"}}}]}, {"name": "MarketAdded", "fields": [{"name": "value", "type": {"defined": {"name": "MarketAddedEvent"}}}]}, {"name": "MarketStatusChanged", "fields": [{"name": "value", "type": {"defined": {"name": "MarketStatusChangedEvent"}}}]}, {"name": "MarketParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "MarketParametersUpdatedEvent"}}}]}, {"name": "FundingParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "FundingParametersUpdatedEvent"}}}]}, {"name": "FeesClaimed", "fields": [{"name": "value", "type": {"defined": {"name": "FeesClaimedEvent"}}}]}, {"name": "PricesUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "PricesUpdatedEvent"}}}]}, {"name": "LiquidationTransferSummary", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationTransferSummaryEvent"}}}]}, {"name": "LiquidationTransfer", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationTransferEvent"}}}]}, {"name": "Liquidation", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationEvent"}}}]}, {"name": "CloseMatchedPositions", "fields": [{"name": "value", "type": {"defined": {"name": "CloseMatchedPositionsEvent"}}}]}, {"name": "NameSuccessor", "fields": [{"name": "value", "type": {"defined": {"name": "NameSuccessorEvent"}}}]}, {"name": "ClaimAuthority", "fields": [{"name": "value", "type": {"defined": {"name": "ClaimAuthorityEvent"}}}]}, {"name": "WithdrawStateTransition", "fields": [{"name": "value", "type": {"defined": {"name": "WithdrawStateTransitionEvent"}}}]}, {"name": "PnL", "fields": [{"name": "value", "type": {"defined": {"name": "PnLEvent"}}}]}, {"name": "StopLossPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossPlacedEvent"}}}]}, {"name": "StopLossCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossCancelledEvent"}}}]}, {"name": "StopLossExecuted", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossExecutedEvent"}}}]}, {"name": "ExchangeStatusChanged", "fields": [{"name": "value", "type": {"defined": {"name": "ExchangeStatusChangedEvent"}}}]}, {"name": "TraderCapabilitiesEnabled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderCapabilitiesEnabledEvent"}}}]}, {"name": "TraderFundsWithdrawnFeePayment", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnFeePaymentEvent"}}}]}, {"name": "OrderPacket", "fields": [{"name": "value", "type": {"defined": {"name": "OrderPacketEvent"}}}]}, {"name": "SetPermission", "fields": [{"name": "value", "type": {"defined": {"name": "SetPermissionEvent"}}}]}, {"name": "AuthorityChanged", "fields": [{"name": "value", "type": {"defined": {"name": "AuthorityChangedEvent"}}}]}, {"name": "TraderDelegated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderDelegatedEvent"}}}]}, {"name": "AdminParameterUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "AdminParameterUpdatedEvent"}}}]}, {"name": "TraderFeesUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFeesUpdatedEvent"}}}]}, {"name": "MarketClosed", "fields": [{"name": "value", "type": {"defined": {"name": "MarketClosedEvent"}}}]}, {"name": "MarketDeleted", "fields": [{"name": "value", "type": {"defined": {"name": "MarketDeletedEvent"}}}]}, {"name": "EscrowAccountCreated", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowAccountCreatedEvent"}}}]}, {"name": "EscrowRequestCreated", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestCreatedEvent"}}}]}, {"name": "EscrowRequestAccepted", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestAcceptedEvent"}}}]}, {"name": "EscrowRequestCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestCancelledEvent"}}}]}, {"name": "SplinePriceUpdatedWithOrdering", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePriceUpdatedWithOrderingEvent"}}}]}, {"name": "SplineParametersUpdatedWithOrdering", "fields": [{"name": "value", "type": {"defined": {"name": "SplineParametersUpdatedWithOrderingEvent"}}}]}, {"name": "TriggerOrderPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderPlacedEvent"}}}]}, {"name": "TriggerOrderCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderCancelledEvent"}}}]}, {"name": "TriggerOrderExecuted", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderExecutedEvent"}}}]}, {"name": "PingInvalidated", "fields": [{"name": "value", "type": {"defined": {"name": "PingInvalidatedEvent"}}}]}, {"name": "PingActivated", "fields": [{"name": "value", "type": {"defined": {"name": "PingActivatedEvent"}}}]}, {"name": "SplinePositionLimitsConfigUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePositionLimitsConfigUpdatedEvent"}}}]}, {"name": "MarketTombstoned", "fields": [{"name": "value", "type": {"defined": {"name": "MarketTombstonedEvent"}}}]}, {"name": "ShutdownClosePositions", "fields": [{"name": "value", "type": {"defined": {"name": "ShutdownClosePositionsEvent"}}}]}, {"name": "OrderResidualDiscarded", "fields": [{"name": "value", "type": {"defined": {"name": "OrderResidualDiscardedEvent"}}}]}]}}, {"name": "SlotContextEvent", "type": {"kind": "struct", "fields": [{"name": "timestamp", "type": "u64"}, {"name": "slot", "type": "u64"}]}}, {"name": "MarketEventHeader", "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "tickSize", "type": "u32"}, {"name": "baseLotDecimals", "type": "i8"}, {"name": "quoteLotDecimals", "type": "u8"}, {"name": "signer", "type": "pubkey"}, {"name": "traderAccount", "type": "pubkey"}]}}, {"name": "OrderPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "orderId", "type": "u32"}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "orderSequenceNumber", "type": "u64"}, {"name": "prevOrderSequenceNumberSlot", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "quantity", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "lastValidSlot", "type": {"defined": {"name": "OptionalNonZeroU64"}}}, {"name": "initialSlot", "type": "u64"}]}}, {"name": "OrderPacketEvent", "type": {"kind": "struct", "fields": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}, {"name": "trader", "type": "pubkey"}, {"name": "nextOrderSequenceNumber", "type": "u64"}]}}, {"name": "OrderRejectedEvent", "type": {"kind": "struct", "fields": [{"name": "orderIndex", "type": "u32"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"array": ["u8", 32]}}]}}, {"name": "OrderResidualDiscardedEvent", "type": {"kind": "struct", "fields": [{"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "baseLotsDiscarded", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"defined": {"name": "OrderResidualDiscardReason"}}}]}}, {"name": "OrderFilledEvent", "type": {"kind": "struct", "fields": [{"name": "orderSequenceNumber", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "quantityRemaining", "type": {"defined": {"name": "BaseLots"}}}, {"name": "maker", "type": "pubkey"}, {"name": "makerFeeRate", "type": {"defined": {"name": "SignedFeeRateMicro"}}}, {"name": "makerBaseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "makerVirtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerQuoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerCumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "SplineFilledEvent", "type": {"kind": "struct", "fields": [{"name": "splineSequenceNumber", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "maker", "type": "pubkey"}, {"name": "makerFeeRate", "type": {"defined": {"name": "SignedFeeRateMicro"}}}, {"name": "makerBaseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "makerVirtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerQuoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerCumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "OrderModifiedEvent", "type": {"kind": "struct", "fields": [{"name": "orderSequenceNumber", "type": "u64"}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsReleased", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "quoteLotsReleased", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "baseLotsRemaining", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"defined": {"name": "OrderModificationReason"}}}]}}, {"name": "TradeSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "tradeSequenceNumber", "type": "u64"}, {"name": "prevTradeSequenceNumberSlot", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "feeInQuoteLots", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "baseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "quoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "cumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "MarketSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "openInterest", "type": {"defined": {"name": "BaseLots"}}}, {"name": "totalMakerQuoteLotFees", "type": {"option": {"defined": {"name": "SignedQuoteLots"}}}}, {"name": "totalTakerQuoteLotFees", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "markPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "spotPrice", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "TraderRegisteredEvent", "type": {"kind": "struct", "fields": [{"name": "traderSequenceNumber", "type": "u64"}, {"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "maxPositions", "type": "u64"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}]}}, {"name": "TraderDelegatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "oldPositionAuthority", "type": "pubkey"}, {"name": "newPositionAuthority", "type": "pubkey"}]}}, {"name": "TraderFeesUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "previousMakerFeeOverrideMultiplier", "type": "i8"}, {"name": "newMakerFeeOverrideMultiplier", "type": "i8"}, {"name": "previousTakerFeeOverrideMultiplier", "type": "i8"}, {"name": "newTakerFeeOverrideMultiplier", "type": "i8"}, {"name": "isHotTrader", "type": "bool"}]}}, {"name": "TraderCapabilitiesEnabledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "previousFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "globalTraderIndex", "type": "u32"}]}}, {"name": "TraderActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "globalTraderIndex", "type": "u32"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "TraderDeactivatedEvent", "type": {"kind": "struct", "fields": [{"name": "prevGlobalTraderIndex", "type": "u32"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "TraderFundsDepositedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "traderFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnFeePaymentEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "fee", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderWithdrawCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderCollateralTransferredEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "srcTrader", "type": "pubkey"}, {"name": "srcTraderSequenceNumber", "type": "u64"}, {"name": "srcTraderPrevSequenceNumberSlot", "type": "u64"}, {"name": "srcTraderNewCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "dstTrader", "type": "pubkey"}, {"name": "dstTraderSequenceNumber", "type": "u64"}, {"name": "dstTraderPrevSequenceNumberSlot", "type": "u64"}, {"name": "dstTraderNewCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "TraderFundingSettledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "fundingPayment", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "cumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}, {"name": "newCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "SplineRegisteredEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "SplineActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}]}}, {"name": "SplineDeactivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "SplinePositionLimitsConfigUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "maxPositionSize", "type": {"option": {"defined": {"name": "PositionSizeLimit"}}}}, {"name": "prevMaxPositionSize", "type": {"defined": {"name": "PositionSizeLimit"}}}, {"name": "leverageDecreaseInBps", "type": {"option": "u32"}}, {"name": "prevLeverageDecreaseInBps", "type": "u32"}]}}, {"name": "SplinePriceUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "priceInTicks", "type": "u64"}, {"name": "userUpdateSlot", "type": "u64"}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "SplineParametersUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}, {"name": "bidRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "askRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}]}}, {"name": "SplinePriceUpdatedWithOrderingEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "priceInTicks", "type": "u64"}, {"name": "userUpdateSlot", "type": "u64"}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}]}}, {"name": "SplineParametersUpdatedWithOrderingEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}, {"name": "bidRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "askRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}]}}, {"name": "StopLossPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "placeSlot", "type": "u64"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}]}}, {"name": "StopLossCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "StopLossExecutedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "TriggerOrderPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}, {"name": "maxSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "fillableSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "usePercent", "type": "bool"}, {"name": "percent", "type": "u8"}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "attachedOrderId", "type": {"defined": {"name": "OptionalFIFOOrderId"}}}]}}, {"name": "TriggerOrderCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "TriggerOrderExecutedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "PingInvalidatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "pre", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "post", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "currentPositionSequenceNumber", "type": "u8"}]}}, {"name": "PingActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "pre", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "post", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "bookOrderRemainingBaseLots", "type": {"option": {"defined": {"name": "BaseLots"}}}}]}}, {"name": "MarketAddedEvent", "type": {"kind": "struct", "fields": []}}, {"name": "MarketStatusChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "newMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}]}}, {"name": "MarketClosedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "finalizedMarkPrice", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "MarketTombstonedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "finalSequenceNumber", "type": "u64"}, {"name": "finalTradeSequenceNumber", "type": "u64"}, {"name": "finalOrderSequenceNumber", "type": "u64"}]}}, {"name": "ShutdownClosePositionsEvent", "type": {"kind": "struct", "fields": [{"name": "longTrader", "type": "pubkey"}, {"name": "shortTrader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "baseLotsClosed", "type": {"defined": {"name": "BaseLots"}}}, {"name": "settlementPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSequenceNumber", "type": "u64"}, {"name": "sequenceNumber", "type": "u64"}]}}, {"name": "MarketDeletedEvent", "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u32"}, {"name": "lamportsReclaimed", "type": "u64"}]}}, {"name": "ExchangeStatusChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousBits", "type": "u8"}, {"name": "newBits", "type": "u8"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "MarketParametersUpdatedEvent", "type": {"kind": "struct", "fields": []}}, {"name": "FundingParametersUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "newFundingIntervalSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "newFundingPeriodSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "newMaxFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}]}}, {"name": "FeesClaimedEvent", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "SetPermissionEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "user", "type": "pubkey"}, {"name": "previousPermission", "type": "u64"}, {"name": "newPermission", "type": "u64"}, {"name": "previousExpiresAtTimestamp", "type": "i64"}, {"name": "newExpiresAtTimestamp", "type": "i64"}, {"name": "previousNumSignerActionsRemaining", "type": "i64"}, {"name": "newNumSignerActionsRemaining", "type": "i64"}, {"name": "created", "type": "bool"}]}}, {"name": "PricesUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "oracleSigner", "type": {"option": "pubkey"}}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "newBestBid", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newBestAsk", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newLastTrade", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newExchangeSpotPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newExchangePerpPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newMidSpotDiffEmaTicks", "type": {"option": {"defined": {"name": "SignedTicks"}}}}, {"name": "newMarkPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "cumulativeFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}, {"name": "settledContribution", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}, {"name": "intervalAccumulator", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLotUpcasted"}}}}, {"name": "assetSequenceNumber", "type": "u64"}, {"name": "prevAssetSequenceNumberSlot", "type": "u64"}]}}, {"name": "LiquidationEvent", "type": {"kind": "struct", "fields": [{"name": "liquidator", "type": "pubkey"}, {"name": "liquidatedTrader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "liquidationSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "markPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "positionClosed", "type": "bool"}]}}, {"name": "LiquidationTransferSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "liquidatee", "type": "pubkey"}, {"name": "liquidator", "type": "pubkey"}, {"name": "totalTransfers", "type": "u32"}, {"name": "liquidateeCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "liquidatorCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "haircutCollected", "type": {"defined": {"name": "QuoteLots"}}}]}}, {"name": "LiquidationTransferEvent", "type": {"kind": "struct", "fields": [{"name": "liquidatee", "type": "pubkey"}, {"name": "liquidator", "type": "pubkey"}, {"name": "assetId", "type": "u64"}, {"name": "baseLotsTransferred", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotsTransferred", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "haircutRate", "type": "u16"}, {"name": "liquidateeCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "liquidatorCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "CloseMatchedPositionsEvent", "type": {"kind": "struct", "fields": [{"name": "caller", "type": "pubkey"}, {"name": "closedShort", "type": "pubkey"}, {"name": "closedLong", "type": "pubkey"}, {"name": "inProfitAccount", "type": "pubkey"}, {"name": "assetId", "type": "u64"}, {"name": "baseLotsClosed", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "atLossCloseValue", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "inProfitCloseValue", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "atLossCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "inProfitCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "NameSuccessorEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}]}}, {"name": "ClaimAuthorityEvent", "type": {"kind": "struct", "fields": [{"name": "previousAuthority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}]}}, {"name": "AuthorityChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousAuthority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}, {"name": "authorityType", "type": {"defined": {"name": "AuthorityType"}}}]}}, {"name": "WithdrawStateTransitionEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "fromState", "type": "u8"}, {"name": "toState", "type": "u8"}, {"name": "reason", "type": "u8"}, {"name": "transitionCount", "type": "u16"}, {"name": "nodeIndex", "type": {"defined": {"name": "NodePointer"}}}]}}, {"name": "PnLEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "realizedPnl", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "fundingPayment", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "baseLotsBefore", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "baseLotsAfter", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotsBefore", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "virtualQuoteLotsAfter", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "AdminParameterUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "assetSymbol", "type": {"option": {"defined": {"name": "Symbol"}}}}, {"name": "assetId", "type": {"option": "u32"}}, {"name": "updateKind", "type": {"defined": {"name": "AdminParameterUpdateKind"}}}]}}, {"name": "EscrowAccountCreatedEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "capacity", "type": "u64"}]}}, {"name": "EscrowRequestCreatedEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "senderAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "senderPdaIndex", "type": "u8"}, {"name": "senderSubaccountIndex", "type": "u8"}, {"name": "receiverPdaIndex", "type": "u8"}, {"name": "receiverSubaccountIndex", "type": "u8"}, {"name": "expirationOffset", "type": "u32"}, {"name": "initialSlot", "type": "u64"}, {"name": "actions", "type": {"array": [{"defined": {"name": "EscrowAction"}}, 4]}}]}}, {"name": "EscrowRequestAcceptedEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}]}}, {"name": "EscrowRequestCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "reason", "type": {"defined": {"name": "EscrowRequestCancelReason"}}}]}}, {"name": "ChangeMarketStatusParams", "docs": ["Borsh payload for changing an orderbook market status."], "type": {"kind": "struct", "fields": [{"name": "nextMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}]}}, {"name": "AuthorizedForceCancelParams", "docs": ["Borsh payload for authority-driven force cancellation by remaining side size."], "type": {"kind": "struct", "fields": [{"name": "remainingBidBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "remainingAskBaseLots", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "AuthorizedForceCancelByIdParams", "docs": ["Borsh payload for authority-driven force cancellation by order id."], "type": {"kind": "struct", "fields": [{"name": "bidOrderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}, {"name": "askOrderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}]}}, {"name": "CreateEscrowAccountParams", "docs": ["Borsh payload for creating an escrow account."], "type": {"kind": "struct", "fields": [{"name": "capacity", "type": "u64"}]}}, {"name": "EscrowParticipantMetadata", "docs": ["Trader PDA/subaccount metadata stored with escrow requests."], "type": {"kind": "struct", "fields": [{"name": "senderPdaIndex", "type": "u8"}, {"name": "senderSubaccountIndex", "type": "u8"}, {"name": "receiverPdaIndex", "type": "u8"}, {"name": "receiverSubaccountIndex", "type": "u8"}]}}, {"name": "CreateEscrowRequestParams", "docs": ["Borsh payload for creating an escrow request."], "type": {"kind": "struct", "fields": [{"name": "participantMetadata", "type": {"defined": {"name": "EscrowParticipantMetadata"}}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "actions", "type": {"vec": {"defined": {"name": "EscrowAction"}}}}]}}, {"name": "CancelEscrowRequestParams", "docs": ["Borsh payload for cancelling an escrow request by sequence number."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}]}}, {"name": "UpsertEscrowRequestParams", "docs": ["Borsh payload for upserting an escrow request."], "type": {"kind": "struct", "fields": [{"name": "participantMetadata", "type": {"defined": {"name": "EscrowParticipantMetadata"}}}, {"name": "action", "type": {"defined": {"name": "EscrowAction"}}}]}}, {"name": "RegisterSplineParams", "docs": ["Empty Borsh payload for registering a spline trader."], "type": {"kind": "struct", "fields": []}}, {"name": "UpdateCommodityMarketStateInstruction", "docs": ["Borsh payload for updating commodity market state and mark price inputs."], "type": {"kind": "struct", "fields": [{"name": "marketState", "type": {"defined": {"name": "CommodityMarketState"}}}, {"name": "lastKnownIndexPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "lastIndexExpiryTimestamp", "type": "u64"}, {"name": "emaPeriodSlots", "type": {"option": "u64"}}]}}, {"name": "UpdatePerpIsolatedOnlyInstruction", "docs": ["Borsh payload for updating isolated-only metadata for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "isolatedOnly", "type": "u8"}]}}, {"name": "UpdatePerpLeverageTiersInstruction", "docs": ["Borsh payload for updating leverage tiers for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "leverageTiers", "type": {"defined": {"name": "LeverageTiers"}}}]}}, {"name": "UpdatePerpOpenInterestCapInstruction", "docs": ["Borsh payload for updating open interest cap for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "openInterestCap", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "UPnlRiskFactor", "docs": ["uPnL risk factor encoded as basis points."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "UpdatePerpUPnlRiskFactorInstruction", "docs": ["Borsh payload for updating uPnL risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "upnlRiskFactor", "type": {"defined": {"name": "UPnlRiskFactor"}}}]}}, {"name": "UpdatePerpUPnlRiskFactorForWithdrawalsInstruction", "docs": ["Borsh payload for updating withdrawal uPnL risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "upnlRiskFactorForWithdrawals", "type": {"defined": {"name": "UPnlRiskFactor"}}}]}}, {"name": "UpdatePerpCancelRiskFactorInstruction", "docs": ["Borsh payload for updating cancel-order risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "cancelOrderRiskFactor", "type": "u16"}]}}, {"name": "UpdatePerpMarkPriceParametersInstruction", "docs": ["Borsh payload for updating mark price calculation parameters for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "emaPeriodSlots", "type": {"option": "u64"}}, {"name": "emaDiffRadius", "type": {"option": "u64"}}, {"name": "bookPriceRadius", "type": {"option": "u64"}}, {"name": "commoditiesAfterHoursRadiusBps", "type": {"option": "u64"}}, {"name": "adjustedExchangeSpotPriceWeight", "type": {"option": "u64"}}, {"name": "bookPriceWeight", "type": {"option": "u64"}}, {"name": "exchangePerpPriceWeight", "type": {"option": "u64"}}, {"name": "spotPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "bookPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "perpPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "riskActionPriceValidityRules", "type": {"option": {"array": [{"array": [{"array": [{"defined": {"name": "ValidationRule"}}, 8]}, 4]}, 8]}}}, {"name": "oracleDivergenceRadius", "type": {"option": "u16"}}, {"name": "minOracleResponses", "type": {"option": "u8"}}]}}, {"name": "UpdateTraderFeesParams", "docs": ["Borsh payload for updating trader fee override multipliers."], "type": {"kind": "struct", "fields": [{"name": "makerFeeOverrideMultiplier", "type": "i8"}, {"name": "takerFeeOverrideMultiplier", "type": "i8"}]}}, {"name": "UpdateMarketFeesParams", "docs": ["Borsh payload for updating market default fees."], "type": {"kind": "struct", "fields": [{"name": "defaultTakerFeeMicro", "type": {"option": "u32"}}, {"name": "defaultMakerFeeMicro", "type": {"option": "i32"}}]}}, {"name": "UpdateWithdrawRateLimitsParams", "docs": ["Borsh payload for updating withdraw throttle rate limits."], "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": {"option": "u64"}}, {"name": "replenishAmountPerSlot", "type": {"option": "u64"}}]}}, {"name": "UpdateWithdrawParametersParams", "docs": ["Borsh payload for updating withdraw cooldown and fee parameters."], "type": {"kind": "struct", "fields": [{"name": "depositCooldownPeriodInSlots", "type": {"option": "u64"}}, {"name": "withdrawalFee", "type": {"option": "u64"}}, {"name": "enqueueingFee", "type": {"option": "u64"}}]}}, {"name": "UpdateAuthoritiesParams", "docs": ["Borsh payload for root authority updates to subordinate authorities."], "type": {"kind": "struct", "fields": [{"name": "newRiskAuthority", "type": {"option": "pubkey"}}, {"name": "newMarketAuthority", "type": {"option": "pubkey"}}, {"name": "newOracleAuthority", "type": {"option": "pubkey"}}, {"name": "newCancelAuthority", "type": {"option": "pubkey"}}, {"name": "newBackstopAuthority", "type": {"option": "pubkey"}}, {"name": "newAdlAuthority", "type": {"option": "pubkey"}}]}}, {"name": "ArenaType", "docs": ["Arena account family to initialize."], "type": {"kind": "enum", "variants": [{"name": "GlobalTraderIndex"}, {"name": "ActiveTraderBuffer"}]}}, {"name": "InitializeArenaParams", "docs": ["Borsh payload for initializing a new arena account."], "type": {"kind": "struct", "fields": [{"name": "arenaType", "type": {"defined": {"name": "ArenaType"}}}]}}, {"name": "RemoveOracleInstruction", "docs": ["Borsh payload for removing oracle public keys from the perp asset map."], "type": {"kind": "struct", "fields": [{"name": "oracleKeys", "type": {"vec": "pubkey"}}]}}, {"name": "RemoveAllOraclesInstruction", "docs": ["Borsh payload for removing all oracle keys for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "UpdateFundingParametersInstruction", "docs": ["Borsh payload for updating funding parameters for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "fundingIntervalSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "fundingPeriodSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "maxFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}]}}, {"name": "UpdateSplinePositionLimitsConfigParams", "docs": ["Borsh payload for updating spline position-size limits and leverage decrease configuration."], "type": {"kind": "struct", "fields": [{"name": "maxPositionSize", "type": {"option": {"defined": {"name": "PositionSizeLimit"}}}}, {"name": "leverageDecreaseInBps", "type": {"option": "u32"}}]}}, {"name": "TraderFundsWithdrawnEnqueuedEvent", "docs": ["Viewer-safe alias for MarketEvent::TraderFundsWithdrawnEnqueued.", "Uses the same payload layout as TraderFundsWithdrawnEvent."], "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnDroppedEvent", "docs": ["Viewer-safe alias for MarketEvent::TraderFundsWithdrawnDropped.", "Uses the same payload layout as TraderFundsWithdrawnEvent."], "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "DirectMarkOverride", "type": {"kind": "struct", "fields": [{"name": "symbol", "type": "string"}, {"name": "target_ticks", "type": "string"}]}}, {"name": "ReferencePriceOverride", "type": {"kind": "struct", "fields": [{"name": "symbol", "type": "string"}, {"name": "spot_ticks", "type": "string"}, {"name": "perp_ticks", "type": "string"}]}}], "events": [{"name": "SlotContextEvent", "docs": ["MarketEvent::SlotContext Borsh variant 0.", "Payload type: SlotContextEvent."], "discriminator": [0]}, {"name": "MarketEventHeader", "docs": ["MarketEvent::Header Borsh variant 1.", "Payload type: MarketEventHeader."], "discriminator": [1]}, {"name": "OrderPlacedEvent", "docs": ["MarketEvent::OrderPlaced Borsh variant 2.", "Payload type: OrderPlacedEvent."], "discriminator": [2]}, {"name": "OrderFilledEvent", "docs": ["MarketEvent::OrderFilled Borsh variant 3.", "Payload type: OrderFilledEvent."], "discriminator": [3]}, {"name": "OrderRejectedEvent", "docs": ["MarketEvent::OrderRejected Borsh variant 4.", "Payload type: OrderRejectedEvent."], "discriminator": [4]}, {"name": "SplineFilledEvent", "docs": ["MarketEvent::SplineFilled Borsh variant 5.", "Payload type: SplineFilledEvent."], "discriminator": [5]}, {"name": "TradeSummaryEvent", "docs": ["MarketEvent::TradeSummary Borsh variant 6.", "Payload type: TradeSummaryEvent."], "discriminator": [6]}, {"name": "OrderModifiedEvent", "docs": ["MarketEvent::OrderModified Borsh variant 7.", "Payload type: OrderModifiedEvent."], "discriminator": [7]}, {"name": "MarketSummaryEvent", "docs": ["MarketEvent::MarketSummary Borsh variant 8.", "Payload type: MarketSummaryEvent."], "discriminator": [8]}, {"name": "TraderRegisteredEvent", "docs": ["MarketEvent::TraderRegistered Borsh variant 9.", "Payload type: TraderRegisteredEvent."], "discriminator": [9]}, {"name": "TraderCollateralTransferredEvent", "docs": ["MarketEvent::TraderCollateralTransferred Borsh variant 10.", "Payload type: TraderCollateralTransferredEvent."], "discriminator": [10]}, {"name": "TraderActivatedEvent", "docs": ["MarketEvent::TraderActivated Borsh variant 11.", "Payload type: TraderActivatedEvent."], "discriminator": [11]}, {"name": "TraderDeactivatedEvent", "docs": ["MarketEvent::TraderDeactivated Borsh variant 12.", "Payload type: TraderDeactivatedEvent."], "discriminator": [12]}, {"name": "TraderFundsDepositedEvent", "docs": ["MarketEvent::TraderFundsDeposited Borsh variant 13.", "Payload type: TraderFundsDepositedEvent."], "discriminator": [13]}, {"name": "TraderFundsWithdrawnEvent", "docs": ["MarketEvent::TraderFundsWithdrawn Borsh variant 14.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [14]}, {"name": "TraderFundsWithdrawnEnqueuedEvent", "docs": ["MarketEvent::TraderFundsWithdrawnEnqueued Borsh variant 15.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [15]}, {"name": "TraderFundsWithdrawnDroppedEvent", "docs": ["MarketEvent::TraderFundsWithdrawnDropped Borsh variant 16.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [16]}, {"name": "TraderWithdrawCancelledEvent", "docs": ["MarketEvent::TraderWithdrawCancelled Borsh variant 17.", "Payload type: TraderWithdrawCancelledEvent."], "discriminator": [17]}, {"name": "TraderFundingSettledEvent", "docs": ["MarketEvent::TraderFundingSettled Borsh variant 18.", "Payload type: TraderFundingSettledEvent."], "discriminator": [18]}, {"name": "SplineRegisteredEvent", "docs": ["MarketEvent::SplineRegistered Borsh variant 19.", "Payload type: SplineRegisteredEvent."], "discriminator": [19]}, {"name": "SplineActivatedEvent", "docs": ["MarketEvent::SplineActivated Borsh variant 20.", "Payload type: SplineActivatedEvent."], "discriminator": [20]}, {"name": "SplineDeactivatedEvent", "docs": ["MarketEvent::SplineDeactivated Borsh variant 21.", "Payload type: SplineDeactivatedEvent."], "discriminator": [21]}, {"name": "SplinePriceUpdatedEvent", "docs": ["MarketEvent::SplinePriceUpdated Borsh variant 22.", "Payload type: SplinePriceUpdatedEvent."], "discriminator": [22]}, {"name": "SplineParametersUpdatedEvent", "docs": ["MarketEvent::SplineParametersUpdated Borsh variant 23.", "Payload type: SplineParametersUpdatedEvent."], "discriminator": [23]}, {"name": "MarketAddedEvent", "docs": ["MarketEvent::MarketAdded Borsh variant 24.", "Payload type: MarketAddedEvent."], "discriminator": [24]}, {"name": "MarketStatusChangedEvent", "docs": ["MarketEvent::MarketStatusChanged Borsh variant 25.", "Payload type: MarketStatusChangedEvent."], "discriminator": [25]}, {"name": "MarketParametersUpdatedEvent", "docs": ["MarketEvent::MarketParametersUpdated Borsh variant 26.", "Payload type: MarketParametersUpdatedEvent."], "discriminator": [26]}, {"name": "FundingParametersUpdatedEvent", "docs": ["MarketEvent::FundingParametersUpdated Borsh variant 27.", "Payload type: FundingParametersUpdatedEvent."], "discriminator": [27]}, {"name": "FeesClaimedEvent", "docs": ["MarketEvent::FeesClaimed Borsh variant 28.", "Payload type: FeesClaimedEvent."], "discriminator": [28]}, {"name": "PricesUpdatedEvent", "docs": ["MarketEvent::PricesUpdated Borsh variant 29.", "Payload type: PricesUpdatedEvent."], "discriminator": [29]}, {"name": "LiquidationTransferSummaryEvent", "docs": ["MarketEvent::LiquidationTransferSummary Borsh variant 30.", "Payload type: LiquidationTransferSummaryEvent."], "discriminator": [30]}, {"name": "LiquidationTransferEvent", "docs": ["MarketEvent::LiquidationTransfer Borsh variant 31.", "Payload type: LiquidationTransferEvent."], "discriminator": [31]}, {"name": "LiquidationEvent", "docs": ["MarketEvent::Liquidation Borsh variant 32.", "Payload type: LiquidationEvent."], "discriminator": [32]}, {"name": "CloseMatchedPositionsEvent", "docs": ["MarketEvent::CloseMatchedPositions Borsh variant 33.", "Payload type: CloseMatchedPositionsEvent."], "discriminator": [33]}, {"name": "NameSuccessorEvent", "docs": ["MarketEvent::NameSuccessor Borsh variant 34.", "Payload type: NameSuccessorEvent."], "discriminator": [34]}, {"name": "ClaimAuthorityEvent", "docs": ["MarketEvent::ClaimAuthority Borsh variant 35.", "Payload type: ClaimAuthorityEvent."], "discriminator": [35]}, {"name": "WithdrawStateTransitionEvent", "docs": ["MarketEvent::WithdrawStateTransition Borsh variant 36.", "Payload type: WithdrawStateTransitionEvent."], "discriminator": [36]}, {"name": "PnLEvent", "docs": ["MarketEvent::PnL Borsh variant 37.", "Payload type: PnLEvent."], "discriminator": [37]}, {"name": "StopLossPlacedEvent", "docs": ["MarketEvent::StopLossPlaced Borsh variant 38.", "Payload type: StopLossPlacedEvent."], "discriminator": [38]}, {"name": "StopLossCancelledEvent", "docs": ["MarketEvent::StopLossCancelled Borsh variant 39.", "Payload type: StopLossCancelledEvent."], "discriminator": [39]}, {"name": "StopLossExecutedEvent", "docs": ["MarketEvent::StopLossExecuted Borsh variant 40.", "Payload type: StopLossExecutedEvent."], "discriminator": [40]}, {"name": "ExchangeStatusChangedEvent", "docs": ["MarketEvent::ExchangeStatusChanged Borsh variant 41.", "Payload type: ExchangeStatusChangedEvent."], "discriminator": [41]}, {"name": "TraderCapabilitiesEnabledEvent", "docs": ["MarketEvent::TraderCapabilitiesEnabled Borsh variant 42.", "Payload type: TraderCapabilitiesEnabledEvent."], "discriminator": [42]}, {"name": "TraderFundsWithdrawnFeePaymentEvent", "docs": ["MarketEvent::TraderFundsWithdrawnFeePayment Borsh variant 43.", "Payload type: TraderFundsWithdrawnFeePaymentEvent."], "discriminator": [43]}, {"name": "OrderPacketEvent", "docs": ["MarketEvent::OrderPacket Borsh variant 44.", "Payload type: OrderPacketEvent."], "discriminator": [44]}, {"name": "SetPermissionEvent", "docs": ["MarketEvent::SetPermission Borsh variant 45.", "Payload type: SetPermissionEvent."], "discriminator": [45]}, {"name": "AuthorityChangedEvent", "docs": ["MarketEvent::AuthorityChanged Borsh variant 46.", "Payload type: AuthorityChangedEvent."], "discriminator": [46]}, {"name": "TraderDelegatedEvent", "docs": ["MarketEvent::TraderDelegated Borsh variant 47.", "Payload type: TraderDelegatedEvent."], "discriminator": [47]}, {"name": "AdminParameterUpdatedEvent", "docs": ["MarketEvent::AdminParameterUpdated Borsh variant 48.", "Payload type: AdminParameterUpdatedEvent."], "discriminator": [48]}, {"name": "TraderFeesUpdatedEvent", "docs": ["MarketEvent::TraderFeesUpdated Borsh variant 49.", "Payload type: TraderFeesUpdatedEvent."], "discriminator": [49]}, {"name": "MarketClosedEvent", "docs": ["MarketEvent::MarketClosed Borsh variant 50.", "Payload type: MarketClosedEvent."], "discriminator": [50]}, {"name": "MarketDeletedEvent", "docs": ["MarketEvent::MarketDeleted Borsh variant 51.", "Payload type: MarketDeletedEvent."], "discriminator": [51]}, {"name": "EscrowAccountCreatedEvent", "docs": ["MarketEvent::EscrowAccountCreated Borsh variant 52.", "Payload type: EscrowAccountCreatedEvent."], "discriminator": [52]}, {"name": "EscrowRequestCreatedEvent", "docs": ["MarketEvent::EscrowRequestCreated Borsh variant 53.", "Payload type: EscrowRequestCreatedEvent."], "discriminator": [53]}, {"name": "EscrowRequestAcceptedEvent", "docs": ["MarketEvent::EscrowRequestAccepted Borsh variant 54.", "Payload type: EscrowRequestAcceptedEvent."], "discriminator": [54]}, {"name": "EscrowRequestCancelledEvent", "docs": ["MarketEvent::EscrowRequestCancelled Borsh variant 55.", "Payload type: EscrowRequestCancelledEvent."], "discriminator": [55]}, {"name": "SplinePriceUpdatedWithOrderingEvent", "docs": ["MarketEvent::SplinePriceUpdatedWithOrdering Borsh variant 56.", "Payload type: SplinePriceUpdatedWithOrderingEvent."], "discriminator": [56]}, {"name": "SplineParametersUpdatedWithOrderingEvent", "docs": ["MarketEvent::SplineParametersUpdatedWithOrdering Borsh variant 57.", "Payload type: SplineParametersUpdatedWithOrderingEvent."], "discriminator": [57]}, {"name": "TriggerOrderPlacedEvent", "docs": ["MarketEvent::TriggerOrderPlaced Borsh variant 58.", "Payload type: TriggerOrderPlacedEvent."], "discriminator": [58]}, {"name": "TriggerOrderCancelledEvent", "docs": ["MarketEvent::TriggerOrderCancelled Borsh variant 59.", "Payload type: TriggerOrderCancelledEvent."], "discriminator": [59]}, {"name": "TriggerOrderExecutedEvent", "docs": ["MarketEvent::TriggerOrderExecuted Borsh variant 60.", "Payload type: TriggerOrderExecutedEvent."], "discriminator": [60]}, {"name": "PingInvalidatedEvent", "docs": ["MarketEvent::PingInvalidated Borsh variant 61.", "Payload type: PingInvalidatedEvent."], "discriminator": [61]}, {"name": "PingActivatedEvent", "docs": ["MarketEvent::PingActivated Borsh variant 62.", "Payload type: PingActivatedEvent."], "discriminator": [62]}, {"name": "SplinePositionLimitsConfigUpdatedEvent", "docs": ["MarketEvent::SplinePositionLimitsConfigUpdated Borsh variant 63.", "Payload type: SplinePositionLimitsConfigUpdatedEvent."], "discriminator": [63]}, {"name": "MarketTombstonedEvent", "docs": ["MarketEvent::MarketTombstoned Borsh variant 64.", "Payload type: MarketTombstonedEvent."], "discriminator": [64]}, {"name": "ShutdownClosePositionsEvent", "docs": ["MarketEvent::ShutdownClosePositions Borsh variant 65.", "Payload type: ShutdownClosePositionsEvent."], "discriminator": [65]}, {"name": "OrderResidualDiscardedEvent", "docs": ["MarketEvent::OrderResidualDiscarded Borsh variant 66.", "Payload type: OrderResidualDiscardedEvent."], "discriminator": [66]}], "errors": []} \ No newline at end of file +{"version": "0.2.0", "address": "EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih", "metadata": {"name": "Eternal", "version": "0.2.0", "spec": "0.1.0", "description": "Phoenix Eternal: a perpetual futures exchange", "address": "EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih", "origin": "anchor-compatible", "codama": {"nodesVersion": "1.6.0", "nodesFromAnchorVersion": "1.4.1", "renderersJsVersion": "2.2.0"}}, "docs": ["Anchor-compatible IDL for the Phoenix Eternal program.", "This IDL is maintained from the native Rust program surface and includes explicit instruction, account, and event discriminators for transaction and account decoding.", "PDA metadata is included where the program defines a canonical derivation; accounts without PDA metadata are supplied as explicit accounts or are stored in GlobalConfiguration."], "instructions": [{"name": "PlaceMarketOrder", "docs": ["Places an immediate-or-cancel order that consumes resting liquidity on the specified market."], "discriminator": [90, 118, 192, 252, 192, 99, 39, 145], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the market order."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}]}, {"name": "PlaceLimitOrder", "docs": ["Places a limit order that may rest on the orderbook or take liquidity depending on matching conditions."], "discriminator": [108, 176, 33, 186, 146, 229, 1, 197], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the limit order."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}]}, {"name": "PlaceMultiLimitOrder", "docs": ["Places multiple post-only limit orders in a single transaction, allowing batch order placement for market making."], "discriminator": [236, 208, 221, 172, 141, 226, 129, 84], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority placing the batch of limit orders."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderPacket", "type": {"defined": {"name": "MultipleOrderPacket"}}}]}, {"name": "CancelOrdersById", "docs": ["Cancels a specific set of resting orders identified by their FIFO order ids."], "discriminator": [234, 204, 126, 94, 222, 22, 141, 24], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "orderIds", "type": {"defined": {"name": "OrderIds"}}}]}, {"name": "CancelUpTo", "docs": ["Cancels resting orders on the specified side up to an optional order or price threshold."], "discriminator": [26, 209, 244, 253, 59, 175, 227, 54], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelUpToInstruction"}}}]}, {"name": "CancelAll", "docs": ["Cancels every resting order owned by the connected trader on the specified market."], "discriminator": [98, 191, 75, 220, 115, 40, 71, 237], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and cross trading is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority requesting the cancellation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the resting orders to cancel."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": []}, {"name": "UpdateOraclePricesWithOrdering", "docs": ["Updates oracle-provided spot and perp prices for one or more perpetual markets with monotonic timestamp ordering."], "discriminator": [164, 0, 14, 250, 58, 198, 25, 24], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA; provides oracle authority and quote decimals for conversions."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Wallet authorised to submit oracle price updates (root oracle or delegated user)."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account recording oracle capabilities and remaining allowances."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perpetual asset map storing mark price state and oracle metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateOraclePricesWithOrderingInstruction"}}}]}, {"name": "UpdateSplinePrice", "docs": ["Updates the mid-price for a spline market-making program on the specified market."], "discriminator": [45, 155, 200, 52, 37, 214, 187, 140], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Authority of the spline trader account authorised to update the spline price."]}, {"name": "traderAccount", "docs": ["Spline trader account whose price is being updated."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection PDA containing the spline configuration and pricing data."]}, {"name": "orderbook", "optional": true, "docs": ["Optional market orderbook account included by some clients as trailing metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplinePriceParamsWithOrdering"}}}]}, {"name": "DepositFunds", "docs": ["Deposits collateral tokens from a trader's token account into the global vault, crediting the trader's account."], "discriminator": [202, 39, 52, 211, 53, 20, 250, 88], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Authority of the trader account making the deposit."]}, {"name": "traderTokenAccount", "writable": true, "docs": ["Trader's token account from which funds are transferred."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA that receives the collateral credit."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account that receives the deposited tokens."]}, {"name": "tokenProgram", "docs": ["SPL Token program for executing the transfer."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "DepositFundsInstruction"}}}]}, {"name": "RegisterTrader", "docs": ["Registers a new trader account with the specified configuration and position capacity."], "discriminator": [75, 243, 224, 167, 1, 5, 51, 32], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that validates the trader registration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Account paying for the trader account creation and rent exemption."]}, {"name": "traderWallet", "docs": ["Authority that will control the trader account; used to derive the trader PDA."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA to be created; derived from [\"trader\", trader_wallet, [trader_pda_index, trader_subaccount_index]]."], "pda": {"seeds": [{"kind": "const", "value": [116, 114, 97, 100, 101, 114]}, {"kind": "account", "path": "traderWallet"}, {"kind": "arg", "path": "params.traderPdaIndex"}, {"kind": "arg", "path": "params.traderSubaccountIndex"}]}}, {"name": "systemProgram", "docs": ["System program for creating the trader account."]}], "args": [{"name": "params", "type": {"defined": {"name": "RegisterTraderParams"}}}]}, {"name": "LiquidateViaMarketOrder", "docs": ["Liquidates a trader's position by executing an immediate-or-cancel market order to reduce their exposure."], "discriminator": [251, 241, 184, 108, 70, 70, 127, 198], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA; enforces the market is registered and liquidation is enabled."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "liquidatorWallet", "signer": true, "docs": ["Liquidator authority executing the liquidation."]}, {"name": "liquidatedTrader", "writable": true, "docs": ["Trader account being liquidated (must be in liquidatable state)."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA (\"trader\" account) holding balances and open positions."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits and funding parameters for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "LiquidationParams"}}}]}, {"name": "LiquidationTransfer", "docs": ["Transfers positions directly between a liquidator and liquidatee once the liquidatee is flagged as unhealthy."], "discriminator": [57, 218, 215, 68, 174, 23, 204, 127], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that owns the global trader index, active trader buffer, and collateral controls."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map governing risk limits, pricing, and funding parameters for every listed market."]}, {"name": "liquidator", "signer": true, "docs": ["Authority of the liquidator who receives transferred positions."]}, {"name": "liquidatorAccount", "writable": true, "docs": ["Liquidator trader account whose balances are credited as transfers are applied."]}, {"name": "liquidateeAccount", "writable": true, "docs": ["Liquidatee trader account whose unhealthy positions are reduced."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "LiquidationTransferParams"}}}]}, {"name": "CloseMatchedPositions", "docs": ["Closes offsetting positions between an at-loss trader and an in-profit trader during ADL."], "discriminator": [112, 174, 32, 120, 93, 113, 10, 161], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that authorizes ADL operations and tracks trader index PDAs."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account containing risk parameters used to validate the ADL transfer."]}, {"name": "caller", "signer": true, "docs": ["ADL authority invoking the matched close instruction."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "atLossAccount", "writable": true, "docs": ["Trader account currently at a loss whose matched exposure will be closed."]}, {"name": "inProfitAccount", "writable": true, "docs": ["Trader account with offsetting profitable exposure to receive the transfer."]}], "args": [{"name": "params", "type": {"defined": {"name": "ClosePositionsParams"}}}]}, {"name": "Log", "docs": ["Internal instruction used by the Phoenix log authority to emit events on-chain.", "This instruction is called via CPI and should not be invoked directly."], "discriminator": [141, 230, 214, 242, 9, 209, 207, 170], "accounts": [{"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA that must sign via CPI."], "signer": true, "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}], "args": [{"name": "eventBatch", "docs": ["Borsh event batch containing the batch index and MarketEvent vector."], "type": {"defined": {"name": "OffChainMarketEvent"}}}]}, {"name": "LogEventLengths", "docs": ["Internal instruction used by the Phoenix log authority to emit per-event byte lengths on-chain.", "This instruction is called via CPI and should not be invoked directly."], "discriminator": [247, 7, 134, 203, 181, 71, 153, 71], "accounts": [{"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA that must sign via CPI."], "signer": true, "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}], "args": [{"name": "eventLengths", "docs": ["Borsh event-length batch containing the batch index and serialized event lengths."], "type": {"defined": {"name": "OffChainMarketEventLengths"}}}]}, {"name": "WithdrawFunds", "docs": ["Withdraws collateral tokens from the global vault to a trader's token account, debiting the trader's account balance."], "discriminator": [241, 36, 29, 111, 208, 31, 104, 217], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Authority of the trader account initiating the withdrawal."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA from which collateral is debited."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map for margin and risk checks."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account from which tokens are withdrawn."]}, {"name": "destinationTokenAccount", "writable": true, "docs": ["Trader's destination token account to receive the withdrawn tokens."]}, {"name": "tokenProgram", "docs": ["SPL Token program for executing the transfer."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "withdrawQueue", "writable": true, "docs": ["Withdraw queue PDA tracking pending withdrawal requests."]}], "args": [{"name": "params", "type": {"defined": {"name": "WithdrawFundsInstruction"}}}]}, {"name": "UpdateSplineParameters", "docs": ["Updates spline curve parameters for automated market making on bid and ask sides."], "discriminator": [241, 91, 139, 170, 18, 41, 203, 22], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Authority authorized to update spline parameters."]}, {"name": "traderAccount", "docs": ["Trader account associated with the spline."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account holding bid and ask curve parameters."]}, {"name": "orderbook", "optional": true, "docs": ["Optional market orderbook account included by some clients as trailing metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplineParametersParamsWithOrdering"}}}]}, {"name": "PlaceStopLoss", "docs": ["Places a stop-loss order that triggers when the market price reaches the specified trigger price."], "discriminator": [236, 46, 164, 66, 235, 92, 162, 137], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer for creating the stop loss account."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet authority."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account PDA to be created or updated."]}, {"name": "systemProgram", "docs": ["System program for creating the stop loss account."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceStopLossInstruction"}}}]}, {"name": "ExecuteStopLoss", "docs": ["Executes a stop-loss order when the trigger conditions are met."], "discriminator": [38, 122, 90, 90, 182, 241, 7, 4], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signer", "signer": true, "docs": ["Signer authorized to execute stop loss orders."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account verifying execution authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "funder", "writable": true, "docs": ["Account to receive rent from closed stop loss account."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to execute and close."]}], "args": [{"name": "params", "type": {"defined": {"name": "ExecuteStopLossInstruction"}}}]}, {"name": "CancelStopLoss", "docs": ["Cancels a pending stop-loss order and closes the stop loss account."], "discriminator": [120, 201, 10, 102, 12, 9, 111, 126], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "funder", "writable": true, "docs": ["Account to receive rent from closed stop loss account."]}, {"name": "traderAccount", "docs": ["Trader account associated with the stop loss order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet authority."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to cancel and close."]}, {"name": "systemProgram", "docs": ["System program for closing the account."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelStopLossInstruction"}}}]}, {"name": "ForceCancelAll", "docs": ["Force cancels all open orders for a trader account. Requires authority authorization."], "discriminator": [201, 0, 188, 127, 41, 55, 80, 48], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority authorized to force cancel orders."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": []}, {"name": "ForceCancelRiskIncreasing", "docs": ["Force cancels risk-increasing orders for a trader to reduce margin exposure."], "discriminator": [41, 55, 217, 35, 92, 72, 42, 238], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that tracks vault and system parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "ForceCancelRiskIncreasingParams"}}}]}, {"name": "UpdateTraderState", "docs": ["Settles funding for a trader and evicts them from the active trader buffer when they no longer have resting orders."], "discriminator": [249, 139, 82, 44, 126, 66, 133, 220], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Read-only global configuration PDA used to verify the perp asset map and buffer keys."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA whose funding snapshot and active status are updated."]}, {"name": "perpAssetMap", "docs": ["Perp asset map providing funding parameters for settlement."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": []}, {"name": "ConsumeWithdrawQueue", "docs": ["Processes pending withdrawals from the withdraw queue."], "discriminator": [228, 40, 211, 139, 39, 212, 116, 113], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalVault", "writable": true, "docs": ["Global vault token account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "withdrawQueue", "writable": true, "docs": ["Withdraw queue account."]}, {"name": "tokenProgram", "docs": ["SPL Token program."]}, {"name": "traderWallet", "docs": ["Trader wallet receiving the withdrawal."]}, {"name": "destinationTokenAccount", "writable": true, "docs": ["Destination token account for withdrawn funds."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}], "args": []}, {"name": "CreatePermission", "docs": ["Creates a new permission account PDA for delegating authority."], "discriminator": [190, 182, 26, 164, 156, 221, 8, 0], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer for account creation rent."]}, {"name": "permission", "writable": true, "docs": ["Permission account PDA to create."]}, {"name": "authority", "docs": ["Authority granting the permission."]}, {"name": "user", "docs": ["User receiving the permission."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "DelegateTrader", "docs": ["Delegates position authority of a trader account to another address."], "discriminator": [3, 164, 192, 106, 119, 91, 122, 224], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Current trader authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA to delegate."]}, {"name": "newPositionAuthority", "docs": ["New position authority address."]}], "args": []}, {"name": "RevokePermission", "docs": ["Revokes permission flags from a permission account."], "discriminator": [116, 82, 33, 181, 121, 144, 249, 227], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority revoking the permission."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission account for delegation."]}, {"name": "targetPermission", "writable": true, "docs": ["Permission account to revoke flags from."]}], "args": [{"name": "params", "type": {"defined": {"name": "RevokePermissionParams"}}}]}, {"name": "SetPermission", "docs": ["Sets permission flags on a permission account."], "discriminator": [70, 126, 41, 194, 245, 189, 128, 8], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "permission", "writable": true, "docs": ["Permission account to modify."]}, {"name": "authority", "signer": true, "docs": ["Authority owning the permission."]}, {"name": "user", "docs": ["User the permission applies to."]}], "args": [{"name": "params", "type": {"defined": {"name": "SetPermissionInstruction"}}}]}, {"name": "SetTraderCapabilitiesDelegated", "docs": ["Sets trader capability flags via a delegated permission."], "discriminator": [207, 170, 17, 21, 53, 35, 88, 151], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority with trader onboarding delegation."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account for delegation validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TraderCapabilityUpdate"}}}]}, {"name": "SetTraderCapability", "docs": ["Sets trader capability flags via risk authority."], "discriminator": [191, 72, 45, 190, 214, 250, 182, 213], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account for delegation validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TraderCapabilityUpdate"}}}]}, {"name": "SyncParentToChild", "docs": ["Syncs capabilities from a parent trader account to a child (isolated margin) account."], "discriminator": [175, 137, 217, 11, 235, 39, 150, 19], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "docs": ["Trader wallet owning both accounts."]}, {"name": "parentTraderAccount", "docs": ["Parent (cross margin) trader account."]}, {"name": "childTraderAccount", "writable": true, "docs": ["Child (isolated margin) trader account to sync."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}], "args": []}, {"name": "TransferCollateral", "docs": ["Transfers collateral between two trader accounts owned by the same wallet."], "discriminator": [157, 163, 63, 27, 242, 72, 251, 97], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader wallet owning both accounts."]}, {"name": "srcTraderAccount", "writable": true, "docs": ["Source trader account."]}, {"name": "dstTraderAccount", "writable": true, "docs": ["Destination trader account."]}, {"name": "perpAssetMap", "docs": ["Perp asset map for margin calculations."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": [{"name": "params", "type": {"defined": {"name": "TransferCollateralInstruction"}}}]}, {"name": "TransferCollateralChildToParent", "docs": ["Transfers collateral from a child (isolated margin) account to its parent (cross margin) account."], "discriminator": [51, 100, 177, 115, 139, 135, 247, 139], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "docs": ["Trader wallet owning both accounts."]}, {"name": "childTraderAccount", "writable": true, "docs": ["Child (isolated margin) trader account."]}, {"name": "parentTraderAccount", "writable": true, "docs": ["Parent (cross margin) trader account."]}, {"name": "perpAssetMap", "docs": ["Perp asset map for margin calculations."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}], "args": []}, {"name": "UncrossCrank", "docs": ["Cranks the orderbook to uncross any crossed orders."], "discriminator": [196, 121, 94, 253, 139, 42, 252, 147], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account."]}], "args": [{"name": "params", "type": {"defined": {"name": "UncrossCrankParams"}}}]}, {"name": "ClearExpiredOrders", "docs": ["Clears expired orders from the orderbook as a maintenance crank operation."], "discriminator": [128, 95, 102, 169, 146, 205, 248, 94], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map tracking positions and risk metrics."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA that tracks market-making programs for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "ClearExpiredOrdersParams"}}}]}, {"name": "CreateConditionalOrdersAccount", "docs": ["Allocates the trader's conditional-orders PDA so further conditional-order placements can store entries."], "discriminator": [201, 6, 112, 61, 231, 15, 57, 248], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Funds the rent for the new conditional-orders PDA."]}, {"name": "traderWallet", "docs": ["Trader authority wallet; not signed here so a sponsorship server may pay rent."]}, {"name": "traderAccount", "docs": ["Trader state PDA whose key seeds the conditional-orders PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Empty account that will be initialised as the conditional-orders collection."]}, {"name": "systemProgram", "docs": ["System program for PDA creation."]}], "args": [{"name": "params", "type": {"defined": {"name": "CreateConditionalOrdersAccountInstruction"}}}]}, {"name": "PlacePositionConditionalOrder", "docs": ["Places a position-based conditional order that fires when mark price crosses the configured trigger."], "discriminator": [65, 108, 83, 129, 76, 193, 92, 143], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any account growth required by the conditional-orders PDA."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA; updated as part of risk validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA for the market."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet authorising the conditional order."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "systemProgram", "docs": ["System program (used for any required reallocations)."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlacePositionConditionalOrderInstruction"}}}]}, {"name": "CancelConditionalOrder", "docs": ["Cancels one or both trigger-order halves of a conditional-order entry."], "discriminator": [82, 104, 25, 51, 248, 54, 66, 184], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA owning the conditional order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelConditionalOrderInstruction"}}}]}, {"name": "PlaceAttachedConditionalOrder", "docs": ["Attaches a conditional order to an existing resting limit order so it fires when that order is filled."], "discriminator": [43, 117, 136, 128, 72, 150, 101, 122], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA owning the parent resting order."]}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account containing the parent limit order."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any conditional-orders PDA reallocation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "systemProgram", "docs": ["System program for PDA reallocation."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceAttachedConditionalOrderInstruction"}}}]}, {"name": "PlaceLimitOrderWithConditionals", "docs": ["Places a limit order and atomically attaches greater/less-than trigger orders to the resulting resting order."], "discriminator": [95, 45, 68, 168, 232, 218, 210, 92], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "payer", "writable": true, "signer": true, "docs": ["Pays for any conditional-orders PDA reallocation."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}, {"name": "systemProgram", "docs": ["System program for PDA reallocation."]}], "args": [{"name": "params", "type": {"defined": {"name": "PlaceLimitOrderWithConditionalsInstruction"}}}]}, {"name": "ExecuteConditionalOrders", "docs": ["Authority-driven execution of a triggered conditional order."], "discriminator": [101, 214, 110, 0, 190, 226, 177, 236], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority (or delegate) authorised to execute conditional orders."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission/delegation PDA validated against the authority."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "ExecuteConditionalOrderInstruction"}}}]}, {"name": "PingConditionalOrders", "docs": ["Authority-driven sweep that activates or invalidates a trader's conditional orders based on current state."], "discriminator": [135, 170, 166, 34, 208, 230, 91, 64], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority (or delegate) authorised to ping conditional orders."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Optional permission/delegation PDA."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": []}, {"name": "CancelAllPlusConditional", "docs": ["Cancels every resting order for a trader on a market and clears the matching conditional orders."], "discriminator": [142, 173, 238, 126, 79, 19, 53, 103], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA holding exchange-wide parameters."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderWallet", "signer": true, "docs": ["Trader authority wallet."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader state PDA."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer account."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account."]}, {"name": "splines", "writable": true, "docs": ["Spline collection PDA."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Trader's conditional-orders collection PDA."]}], "args": []}, {"name": "MarketClosedForceCancelAll", "docs": ["Force cancels all open orders and conditional orders for a trader after a market has closed."], "discriminator": [229, 101, 143, 249, 150, 175, 175, 219], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}, {"name": "traderConditionalOrders", "writable": true, "docs": ["Optional conditional orders account for the trader; clears stop loss and trigger state when present."]}], "args": []}, {"name": "ForceCancelAfterHours", "docs": ["Force cancels orders that are invalid during commodity after-hours trading."], "discriminator": [147, 13, 224, 61, 248, 234, 64, 164], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": []}, {"name": "AuthorizedForceCancel", "docs": ["Force cancels orders under the configured cancel authority until the requested side sizes remain."], "discriminator": [13, 22, 221, 234, 211, 165, 172, 2], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "cancelAuthority", "signer": true, "docs": ["Configured cancel authority signer."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "AuthorizedForceCancelParams"}}}]}, {"name": "AuthorizedForceCancelById", "docs": ["Force cancels specific bid and ask orders under the configured cancel authority."], "discriminator": [122, 159, 12, 159, 144, 108, 225, 241], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "cancelAuthority", "signer": true, "docs": ["Configured cancel authority signer."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account PDA that holds position and order state."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account used for risk and market metadata validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Market orderbook account that stores resting liquidity."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for this market."]}], "args": [{"name": "params", "type": {"defined": {"name": "AuthorizedForceCancelByIdParams"}}}]}, {"name": "ChangeMarketStatus", "docs": ["Changes an orderbook market status through the market authority path."], "discriminator": [221, 127, 224, 41, 177, 145, 126, 8], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account whose market status is updated."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map used to validate and update market metadata."]}], "args": [{"name": "params", "type": {"defined": {"name": "ChangeMarketStatusParams"}}}]}, {"name": "CreateEscrowAccount", "docs": ["Creates or initializes the escrow PDA for a trader wallet."], "discriminator": [146, 147, 225, 47, 51, 64, 112, 1], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "payer", "writable": true, "signer": true, "docs": ["Payer funding escrow account creation."]}, {"name": "traderWallet", "docs": ["Trader wallet that owns the escrow account."]}, {"name": "traderEscrow", "writable": true, "docs": ["Escrow account PDA for the trader wallet."]}, {"name": "systemProgram", "docs": ["System program for creating the escrow account."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "CreateEscrowAccountParams"}}}]}, {"name": "CreateEscrowRequest", "docs": ["Creates an escrow request between sender and receiver trader accounts."], "discriminator": [116, 164, 236, 204, 187, 132, 195, 219], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "signer": true, "docs": ["Sender wallet signing the escrow request."]}, {"name": "senderTraderAccount", "docs": ["Sender trader account whose authority is validated."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account authorizing delegated escrow request creation."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverTraderAccount", "docs": ["Receiver trader account whose authority is validated."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account holding pending requests."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "CreateEscrowRequestParams"}}}]}, {"name": "AcceptEscrowRequest", "docs": ["Accepts a pending escrow request and applies its transfer actions."], "discriminator": [182, 41, 62, 227, 228, 175, 22, 137], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "docs": ["Sender wallet for the pending escrow request."]}, {"name": "senderTraderAccount", "writable": true, "docs": ["Sender trader account affected by accepted escrow actions."]}, {"name": "receiverWallet", "signer": true, "docs": ["Receiver wallet accepting the escrow request."]}, {"name": "receiverTraderAccount", "writable": true, "docs": ["Receiver trader account affected by accepted escrow actions."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account containing the pending request."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": []}, {"name": "CancelEscrowRequest", "docs": ["Cancels a pending escrow request by sequence number."], "discriminator": [202, 26, 163, 239, 32, 8, 85, 2], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerWallet", "signer": true, "docs": ["Sender or receiver wallet authorized to cancel the escrow request."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account containing the request."]}], "args": [{"name": "params", "type": {"defined": {"name": "CancelEscrowRequestParams"}}}]}, {"name": "UpsertEscrowRequest", "docs": ["Creates or increments a cash escrow request for a sender and receiver pair."], "discriminator": [213, 56, 86, 93, 34, 103, 160, 99], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "senderWallet", "signer": true, "docs": ["Sender wallet signing the escrow upsert."]}, {"name": "senderTraderAccount", "docs": ["Sender trader account whose authority is validated."]}, {"name": "permissionAccount", "writable": true, "docs": ["Permission account authorizing delegated escrow request creation."]}, {"name": "receiverWallet", "docs": ["Receiver wallet that owns the escrow account."]}, {"name": "receiverTraderAccount", "docs": ["Receiver trader account whose authority is validated."]}, {"name": "receiverEscrow", "writable": true, "docs": ["Receiver escrow account holding pending requests."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for margin validation."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpsertEscrowRequestParams"}}}]}, {"name": "RegisterSpline", "docs": ["Registers a trader as an active spline participant for a market."], "discriminator": [30, 176, 160, 79, 252, 117, 241, 58], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "payerAccount", "writable": true, "signer": true, "docs": ["Payer funding any required spline account growth."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account for the market."]}, {"name": "marketAccount", "docs": ["Market orderbook account for the spline."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account being registered as a spline participant."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used for isolated-only validation."]}, {"name": "systemProgram", "docs": ["System program for rent transfers or account growth."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "RegisterSplineParams"}}}]}, {"name": "UpdateCommodityMarketState", "docs": ["Updates commodity market state and related mark price inputs."], "discriminator": [45, 245, 97, 184, 200, 147, 142, 73], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or commodity-market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account updated with commodity market state."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account for the commodity market."]}, {"name": "splines", "writable": true, "docs": ["Spline collection account for the commodity market."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateCommodityMarketStateInstruction"}}}]}, {"name": "UpdatePerpIsolatedOnly", "docs": ["Updates the isolated-only flag for a perp asset."], "discriminator": [101, 56, 163, 250, 73, 35, 115, 91], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpIsolatedOnlyInstruction"}}}]}, {"name": "UpdatePerpLeverageTiers", "docs": ["Updates leverage tiers for a perp asset."], "discriminator": [110, 223, 244, 99, 34, 83, 97, 121], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpLeverageTiersInstruction"}}}]}, {"name": "UpdatePerpOpenInterestCap", "docs": ["Updates open interest cap for a perp asset."], "discriminator": [75, 137, 116, 5, 80, 123, 107, 177], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpOpenInterestCapInstruction"}}}]}, {"name": "UpdatePerpUPnlRiskFactor", "docs": ["Updates uPnL risk factor for a perp asset."], "discriminator": [113, 214, 226, 254, 147, 9, 94, 220], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpUPnlRiskFactorInstruction"}}}]}, {"name": "UpdatePerpUPnlRiskFactorForWithdrawals", "docs": ["Updates withdrawal uPnL risk factor for a perp asset."], "discriminator": [252, 32, 10, 88, 238, 245, 104, 59], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpUPnlRiskFactorForWithdrawalsInstruction"}}}]}, {"name": "UpdatePerpCancelRiskFactor", "docs": ["Updates cancel-order risk factor for a perp asset."], "discriminator": [88, 6, 185, 205, 118, 28, 129, 242], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpCancelRiskFactorInstruction"}}}]}, {"name": "UpdatePerpMarkPriceParameters", "docs": ["Updates mark price calculation parameters for a perp asset."], "discriminator": [254, 86, 13, 94, 43, 224, 160, 226], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdatePerpMarkPriceParametersInstruction"}}}]}, {"name": "UpdateTraderFees", "docs": ["Updates fee override multipliers for a trader."], "discriminator": [182, 233, 57, 205, 174, 168, 73, 237], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or fee-update delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account whose fee overrides are updated."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index account; trailing arena accounts may follow in the transaction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateTraderFeesParams"}}}]}, {"name": "UpdateMarketFees", "docs": ["Updates default fee parameters for a market orderbook."], "discriminator": [187, 36, 121, 171, 131, 19, 243, 117], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbookAccount", "writable": true, "docs": ["Orderbook account whose default fee parameters are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateMarketFeesParams"}}}]}, {"name": "UpdateWithdrawRateLimits", "docs": ["Updates withdrawal throttle budget and replenishment parameters."], "discriminator": [187, 246, 146, 182, 141, 216, 36, 1], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority or withdrawal-suspension delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "withdrawQueueAccount", "writable": true, "docs": ["Withdraw queue account whose throttle settings are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateWithdrawRateLimitsParams"}}}]}, {"name": "UpdateWithdrawParameters", "docs": ["Updates withdraw cooldown and fee parameters."], "discriminator": [87, 209, 225, 132, 91, 151, 249, 132], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "withdrawQueueAccount", "writable": true, "docs": ["Withdraw queue account whose fee parameters are updated."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateWithdrawParametersParams"}}}]}, {"name": "UpdateAuthorities", "docs": ["Updates subordinate authorities stored in global configuration."], "discriminator": [175, 228, 137, 18, 175, 70, 220, 165], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "writable": true, "docs": ["Global configuration PDA that stores mutable exchange-wide configuration."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority or permitted delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateAuthoritiesParams"}}}]}, {"name": "InitializeArena", "docs": ["Initializes a new global trader index or active trader buffer arena account."], "discriminator": [11, 37, 221, 1, 205, 120, 25, 230], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority or arena-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "headerAccount", "writable": true, "docs": ["Arena family header account."]}, {"name": "newArenaAccount", "writable": true, "docs": ["New arena account to create and initialize."]}, {"name": "systemProgram", "docs": ["System program for creating the arena account."], "address": "11111111111111111111111111111111"}], "args": [{"name": "params", "type": {"defined": {"name": "InitializeArenaParams"}}}]}, {"name": "RemoveEmptyArena", "docs": ["Removes an empty active trader buffer arena account."], "discriminator": [155, 65, 162, 79, 211, 221, 197, 168], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Risk authority or arena-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "headerAccount", "writable": true, "docs": ["Active trader buffer header account."]}, {"name": "removedAccount", "writable": true, "docs": ["Empty arena account to remove."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "RemoveOracle", "docs": ["Removes one or more oracle public keys from the perp asset map."], "discriminator": [60, 93, 51, 197, 182, 42, 170, 26], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Oracle authority or oracle-update delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "RemoveOracleInstruction"}}}]}, {"name": "RemoveAllOracles", "docs": ["Removes all oracle public keys for a perp asset."], "discriminator": [164, 64, 91, 210, 137, 189, 135, 38], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Root authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "RemoveAllOraclesInstruction"}}}]}, {"name": "SetPermissionDelegated", "docs": ["Sets a whitelisted permission on a target permission account through delegated authority."], "discriminator": [35, 25, 57, 68, 255, 26, 161, 229], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Authority with permission to set delegated permissions."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "targetPermission", "writable": true, "docs": ["Permission account whose flags are updated."]}, {"name": "from", "docs": ["Authority seed of the target permission PDA."]}, {"name": "to", "docs": ["User seed of the target permission PDA."]}], "args": [{"name": "params", "type": {"defined": {"name": "SetPermissionInstruction"}}}]}, {"name": "DeactivateSpline", "docs": ["Deactivates a trader spline for a market."], "discriminator": [0, 254, 22, 100, 238, 197, 118, 181], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account for the market."]}, {"name": "marketAccount", "docs": ["Market orderbook account for the spline."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "traderAccount", "writable": true, "docs": ["Trader account being deactivated as a spline participant."]}, {"name": "perpAssetMap", "docs": ["Perp asset map used while updating trader activity."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "ForceCancelStopLoss", "docs": ["Force cancels stop loss state for a trader after market closure."], "discriminator": [114, 230, 115, 170, 66, 79, 19, 172], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "docs": ["Closed market orderbook associated with the stop loss."]}, {"name": "funder", "writable": true, "docs": ["Funder account receiving reclaimed stop loss rent."]}, {"name": "traderAccount", "docs": ["Trader account that owns the stop loss."]}, {"name": "stopLossAccount", "writable": true, "docs": ["Stop loss account to close."]}, {"name": "systemProgram", "docs": ["System program."], "address": "11111111111111111111111111111111"}], "args": []}, {"name": "UpdateFundingParameters", "docs": ["Updates funding parameters for a perp asset."], "discriminator": [101, 195, 104, 104, 116, 20, 223, 188], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account whose metadata is updated by this instruction."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateFundingParametersInstruction"}}}]}, {"name": "CloseMarket", "docs": ["Closes a market and records its finalized mark price."], "discriminator": [88, 154, 248, 186, 48, 14, 123, 244], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account updated with finalized mark price."]}, {"name": "orderbook", "writable": true, "docs": ["Orderbook account whose status is moved to Closed."]}], "args": []}, {"name": "TombstoneMarket", "docs": ["Tombstones a closed market after open interest, orders, and splines are cleared."], "discriminator": [90, 177, 17, 88, 85, 11, 177, 96], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "orderbook", "writable": true, "docs": ["Closed orderbook account to tombstone."]}, {"name": "perpAssetMap", "docs": ["Perp asset map account used to verify open interest is zero."]}, {"name": "splineCollection", "docs": ["Spline collection account used to verify no active splines remain."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}], "args": []}, {"name": "SettleClosedMarketPosition", "docs": ["Settles matching long and short trader positions in a closed market."], "discriminator": [27, 222, 221, 233, 136, 15, 254, 210], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority or market-management delegate."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account containing finalized mark price."]}, {"name": "orderbook", "writable": true, "docs": ["Closed orderbook account for the market."]}, {"name": "globalTraderIndex", "writable": true, "docs": ["Global trader index header account; trailing arena accounts may follow in the transaction."]}, {"name": "activeTraderBuffer", "writable": true, "docs": ["Active trader buffer header account; trailing arena accounts may follow in the transaction."]}, {"name": "longTrader", "writable": true, "docs": ["Trader account with a long position to settle."]}, {"name": "shortTrader", "writable": true, "docs": ["Trader account with a short position to settle."]}], "args": []}, {"name": "DeleteTombstonedMarket", "docs": ["Deletes a tombstoned market and reclaims orderbook and spline rent."], "discriminator": [146, 17, 252, 116, 222, 113, 52, 102], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "globalConfiguration", "docs": ["Global configuration PDA used to validate exchange-wide account relationships."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "authority", "signer": true, "docs": ["Market authority."]}, {"name": "maybePermissionAccount", "writable": true, "docs": ["Permission account used for delegated authority validation."]}, {"name": "lamportRecipient", "writable": true, "docs": ["Account receiving reclaimed lamports."]}, {"name": "perpAssetMap", "writable": true, "docs": ["Perp asset map account from which the market metadata is removed."]}, {"name": "orderbook", "writable": true, "docs": ["Tombstoned orderbook account to delete."]}, {"name": "splineCollection", "writable": true, "docs": ["Spline collection account to delete."]}], "args": []}, {"name": "UpdateSplinePositionLimitsConfig", "docs": ["Updates position-size limits and leverage decrease configuration for a spline trader."], "discriminator": [96, 60, 5, 112, 19, 144, 13, 107], "accounts": [{"name": "phoenixProgram", "docs": ["Phoenix Eternal program id; logged to assert the correct executable is invoked."]}, {"name": "phoenixLogAuthority", "docs": ["Phoenix log authority PDA used for emitting on-chain market events."], "pda": {"seeds": [{"kind": "const", "value": [108, 111, 103]}], "program": {"kind": "const", "value": [206, 112, 214, 251, 86, 142, 67, 235, 252, 150, 74, 144, 21, 108, 59, 233, 86, 59, 8, 23, 6, 124, 171, 105, 78, 188, 131, 239, 221, 179, 126, 134]}}}, {"name": "signerAccount", "signer": true, "docs": ["Trader or authority signer updating spline position limits."]}, {"name": "traderAccount", "docs": ["Trader account associated with the spline."]}, {"name": "splineAccount", "writable": true, "docs": ["Spline collection account holding the trader spline."]}], "args": [{"name": "params", "type": {"defined": {"name": "UpdateSplinePositionLimitsConfigParams"}}}]}], "accounts": [{"name": "GlobalConfiguration", "discriminator": [37, 146, 212, 210, 47, 136, 111, 20], "docs": ["Global exchange configuration account derived from the \"global\" PDA seed.", "Stores configured authorities, canonical collateral mint, global vault, PerpAssetMap, GlobalTraderIndex, ActiveTraderBuffer, and WithdrawQueue addresses."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108]}]}}, {"name": "OrderbookHeader", "discriminator": [65, 1, 40, 241, 166, 14, 86, 188], "docs": ["Market orderbook account header for a perpetual market.", "Market orderbooks are explicit market accounts created during AddMarket and recorded in PerpAssetMap metadata; they are not PDAs."]}, {"name": "SplineCollectionHeader", "discriminator": [192, 245, 182, 191, 88, 37, 79, 48], "docs": ["Spline collection account header derived from [\"spline\", orderbook].", "Stores spline market-making state for one market."], "pda": {"seeds": [{"kind": "const", "value": [115, 112, 108, 105, 110, 101]}, {"kind": "account", "path": "marketAccount", "account": "OrderbookHeader"}]}}, {"name": "Trader", "discriminator": [41, 97, 73, 105, 110, 214, 112, 9], "docs": ["Trader account derived from [\"trader\", authority, trader_pda_index, trader_subaccount_index].", "Stores collateral, positions, open orders, and per-trader risk state."], "pda": {"seeds": [{"kind": "const", "value": [116, 114, 97, 100, 101, 114]}, {"kind": "account", "path": "authority"}, {"kind": "arg", "path": "traderPdaIndex"}, {"kind": "arg", "path": "traderSubaccountIndex"}]}}, {"name": "ActiveTraderBufferHeader", "discriminator": [192, 255, 205, 165, 80, 154, 131, 5], "docs": ["Header arena for the active trader buffer derived from [\"active_trader_buffer\", 0u16]."], "pda": {"seeds": [{"kind": "const", "value": [97, 99, 116, 105, 118, 101, 95, 116, 114, 97, 100, 101, 114, 95, 98, 117, 102, 102, 101, 114]}, {"kind": "const", "value": [0, 0]}]}}, {"name": "ActiveTraderBufferArenaHeader", "discriminator": [6, 249, 57, 137, 82, 121, 191, 107], "docs": ["Additional active trader buffer arena derived from [\"active_trader_buffer\", arena_index]."], "pda": {"seeds": [{"kind": "const", "value": [97, 99, 116, 105, 118, 101, 95, 116, 114, 97, 100, 101, 114, 95, 98, 117, 102, 102, 101, 114]}, {"kind": "arg", "path": "arenaIndex"}]}}, {"name": "GlobalTraderIndexHeader", "discriminator": [145, 92, 169, 6, 5, 144, 1, 205], "docs": ["Header arena for the global trader index derived from [\"global_trader_index\", 0u16]."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108, 95, 116, 114, 97, 100, 101, 114, 95, 105, 110, 100, 101, 120]}, {"kind": "const", "value": [0, 0]}]}}, {"name": "GlobalTraderIndexArenaHeader", "discriminator": [98, 110, 179, 71, 203, 120, 200, 235], "docs": ["Additional global trader index arena derived from [\"global_trader_index\", arena_index]."], "pda": {"seeds": [{"kind": "const", "value": [103, 108, 111, 98, 97, 108, 95, 116, 114, 97, 100, 101, 114, 95, 105, 110, 100, 101, 120]}, {"kind": "arg", "path": "arenaIndex"}]}}, {"name": "PerpAssetMap", "discriminator": [142, 54, 115, 223, 124, 239, 108, 38], "docs": ["Perpetual asset map account storing market metadata, risk parameters, oracle metadata, and funding state.", "The account pubkey is stored in GlobalConfiguration rather than derived from a fixed PDA seed; it is not a PDA."]}, {"name": "WithdrawQueueHeader", "discriminator": [125, 59, 94, 108, 171, 16, 43, 228], "docs": ["Withdraw queue account storing pending withdrawals and withdrawal throttle state.", "The account pubkey is stored in GlobalConfiguration rather than derived from a fixed PDA seed; it is not a PDA."]}, {"name": "PermissionAccount", "discriminator": [180, 172, 109, 215, 129, 165, 97, 38], "docs": ["Permission delegation account derived from [\"permission\", authority, user].", "Stores delegated capability bits and optional expiry/usage constraints."], "pda": {"seeds": [{"kind": "const", "value": [112, 101, 114, 109, 105, 115, 115, 105, 111, 110]}, {"kind": "account", "path": "authority"}, {"kind": "account", "path": "user"}]}}, {"name": "StopLosses", "discriminator": [127, 64, 139, 143, 70, 43, 167, 152], "docs": ["Stop-loss account derived from [\"stoploss\", trader_account, asset_id].", "Stores trigger orders for one trader and market."], "pda": {"seeds": [{"kind": "const", "value": [115, 116, 111, 112, 108, 111, 115, 115]}, {"kind": "account", "path": "traderAccount", "account": "Trader"}, {"kind": "arg", "path": "assetId"}]}}, {"name": "EscrowHeader", "discriminator": [50, 4, 48, 59, 209, 138, 251, 204], "docs": ["Escrow inbox account derived from [\"escrow\", authority].", "Stores pending escrow requests for the authority."], "pda": {"seeds": [{"kind": "const", "value": [101, 115, 99, 114, 111, 119]}, {"kind": "account", "path": "authority"}]}}, {"name": "ConditionalOrderHeader", "discriminator": [147, 116, 229, 177, 249, 200, 146, 49], "docs": ["Conditional orders account derived from [\"conditional_orders\", trader_account].", "Stores attached and position-scoped conditional orders for a trader account."], "pda": {"seeds": [{"kind": "const", "value": [99, 111, 110, 100, 105, 116, 105, 111, 110, 97, 108, 95, 111, 114, 100, 101, 114, 115]}, {"kind": "account", "path": "traderAccount", "account": "Trader"}]}}], "types": [{"name": "OrderPacket", "docs": ["Serialized parameters for Phoenix order placement instructions."], "type": {"kind": "struct", "fields": [{"name": "kind", "type": {"defined": {"name": "OrderPacketKind"}}}]}}, {"name": "OrderPacketKind", "docs": ["Supported order variants accepted by the Phoenix matching engine."], "type": {"kind": "enum", "variants": [{"name": "PostOnly", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "slide", "type": "bool"}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}, {"name": "Limit", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "selfTradeBehavior", "type": {"defined": {"name": "SelfTradeBehavior"}}}, {"name": "matchLimit", "type": {"option": "u64"}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}, {"name": "ImmediateOrCancel", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "priceInTicks", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "numQuoteLots", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "minBaseLotsToFill", "type": {"defined": {"name": "BaseLots"}}}, {"name": "minQuoteLotsToFill", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "selfTradeBehavior", "type": {"defined": {"name": "SelfTradeBehavior"}}}, {"name": "matchLimit", "type": {"option": "u64"}}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "cancelExisting", "type": "bool"}]}]}}, {"name": "Side", "docs": ["Trading direction for an order."], "type": {"kind": "enum", "variants": [{"name": "Bid"}, {"name": "Ask"}]}}, {"name": "SelfTradeBehavior", "docs": ["Policy for handling matches against the submitting trader's resting orders."], "type": {"kind": "enum", "variants": [{"name": "Abort"}, {"name": "CancelProvide"}, {"name": "DecrementTake"}]}}, {"name": "BaseLots", "docs": ["Quantity wrapper representing discrete base lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "QuoteLots", "docs": ["Quantity wrapper representing discrete quote lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "Ticks", "docs": ["Discrete price unit measured in quote lots per base lot."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "BaseLotsPerTick", "docs": ["Density of liquidity per tick for spline market making."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OrderFlags", "docs": ["Bitmask flags applied to an order (currently only the reduce-only bit is used)."], "type": {"kind": "struct", "fields": [{"name": "flags", "type": "u8"}]}}, {"name": "OrderIds", "docs": ["Collection of cancel identifiers used to target specific resting orders."], "type": {"kind": "struct", "fields": [{"name": "orderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}]}}, {"name": "CancelUpToInstruction", "docs": ["Parameters controlling cancel-up-to behaviour."], "type": {"kind": "struct", "fields": [{"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "numOrdersToCancel", "type": {"option": "u64"}}, {"name": "tickLimit", "type": {"option": "u64"}}]}}, {"name": "CancelId", "docs": ["Identifies a resting order along with an optional hint into the orderbook."], "type": {"kind": "struct", "fields": [{"name": "nodePointer", "type": {"defined": {"name": "NodePointer"}}}, {"name": "orderId", "type": {"defined": {"name": "FIFOOrderId"}}}]}}, {"name": "FIFOOrderId", "docs": ["Stable identifier for a resting order composed of price ticks and sequence number."], "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "NodePointer", "docs": ["Pointer hint into the orderbook linked list; zero represents a null pointer."], "type": {"kind": "struct", "fields": [{"name": "value", "type": "u32"}]}}, {"name": "Symbol", "docs": ["Fixed-width (16 byte) asset symbol stored as UTF-8 with trailing zero padding."], "type": {"kind": "struct", "fields": [{"name": "symbolBytes", "type": {"array": ["u8", 16]}}]}}, {"name": "Price", "docs": ["Price representation composed of a mantissa (`value`) and decimal exponent (`expo`)."], "type": {"kind": "struct", "fields": [{"name": "value", "type": "u64"}, {"name": "expo", "type": "u8"}]}}, {"name": "OraclePriceUpdate", "docs": ["Single oracle price update targeting a specific perpetual asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetId", "type": {"defined": {"name": "Symbol"}}}, {"name": "newExchangePerpPrice", "type": {"option": {"defined": {"name": "Price"}}}}, {"name": "newExchangeSpotPrice", "type": {"defined": {"name": "Price"}}}]}}, {"name": "UpdateOraclePricesWithOrderingInstruction", "docs": ["Oracle instruction payload containing ordered price updates and timestamp reset policy."], "type": {"kind": "struct", "fields": [{"name": "updateTimestamp", "type": "u64"}, {"name": "updates", "type": {"vec": {"defined": {"name": "OraclePriceUpdate"}}}}, {"name": "shouldResetTimestamp", "type": "bool"}]}}, {"name": "UpdateSplinePriceParams", "docs": ["Parameters for updating a spline's mid-price."], "type": {"kind": "struct", "fields": [{"name": "newMidPrice", "type": "u64"}, {"name": "userUpdateSlot", "type": {"option": "u64"}}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "UpdateSplinePriceParamsWithOrdering", "docs": ["Parameters for updating a spline's mid-price with anti-reordering protection."], "type": {"kind": "struct", "fields": [{"name": "newMidPrice", "type": "u64"}, {"name": "userUpdateSlot", "type": {"option": "u64"}}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "overrideSequenceNumber", "type": "bool"}]}}, {"name": "DepositFundsInstruction", "docs": ["Parameters for depositing collateral into the global vault."], "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "RegisterTraderParams", "docs": ["Parameters for registering a new trader account."], "type": {"kind": "struct", "fields": [{"name": "maxPositions", "type": "u64"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}]}}, {"name": "LiquidationParams", "docs": ["Parameters for liquidating a trader's position via market order."], "type": {"kind": "struct", "fields": [{"name": "assetMint", "type": "pubkey"}, {"name": "liquidationSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "liquidationPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "fillOrKill", "type": "bool"}]}}, {"name": "TransferRequest", "docs": ["Transfer payload describing how many base lots of an asset should move from the liquidatee to the liquidator."], "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u64"}, {"name": "amount", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "LiquidationTransferParams", "docs": ["Parameters controlling liquidation transfers between traders."], "type": {"kind": "struct", "fields": [{"name": "transferRequests", "type": {"vec": {"defined": {"name": "TransferRequest"}}}}, {"name": "executeAllTransfers", "type": "bool"}]}}, {"name": "ClosePositionsParams", "docs": ["Parameters for closing matched positions between at-loss and in-profit traders."], "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u64"}, {"name": "amount", "type": {"defined": {"name": "BaseLots"}}}, {"name": "atLossCloseValue", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "inProfitCloseValue", "type": {"option": {"defined": {"name": "QuoteLots"}}}}]}}, {"name": "MultipleOrderPacket", "docs": ["Batch order packet for placing multiple post-only limit orders in a single transaction."], "type": {"kind": "struct", "fields": [{"name": "bids", "type": {"vec": {"defined": {"name": "CondensedOrder"}}}}, {"name": "asks", "type": {"vec": {"defined": {"name": "CondensedOrder"}}}}, {"name": "clientOrderId", "type": {"option": {"array": ["u8", 16]}}}, {"name": "slide", "type": "bool"}]}}, {"name": "CondensedOrder", "docs": ["Condensed order representation for batch order placement (price, size, and optional TiF)."], "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": "u64"}, {"name": "sizeInBaseLots", "type": "u64"}, {"name": "lastValidSlot", "type": {"option": "u64"}}]}}, {"name": "WithdrawFundsInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64", "docs": ["Amount in quote lots to withdraw."]}]}}, {"name": "UpdateSplineParametersParams", "type": {"kind": "struct", "fields": [{"name": "bidRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "askRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "UpdateSplineParametersParamsWithOrdering", "type": {"kind": "struct", "fields": [{"name": "bidRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "askRegions", "type": {"vec": {"defined": {"name": "TickRegionParams"}}}}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "overrideSequenceNumber", "type": "bool"}]}}, {"name": "TickRegionParams", "type": {"kind": "struct", "fields": [{"name": "startOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "endOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "density", "type": {"defined": {"name": "BaseLotsPerTick"}}}, {"name": "lifespan", "type": "u64"}]}}, {"name": "PlaceStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "triggerPrice", "type": "u64"}, {"name": "executionPrice", "type": "u64"}, {"name": "tradeSize", "type": "u64"}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}]}}, {"name": "ExecuteStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "CancelStopLossInstruction", "type": {"kind": "struct", "fields": [{"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "ForceCancelRiskIncreasingParams", "type": {"kind": "struct", "fields": []}}, {"name": "Direction", "type": {"kind": "enum", "variants": [{"name": "GreaterThan"}, {"name": "LessThan"}]}}, {"name": "StopLossOrderKind", "type": {"kind": "enum", "variants": [{"name": "IOC"}, {"name": "Limit"}]}}, {"name": "LogHeader", "type": {"kind": "struct", "fields": [{"name": "logBatchIndex", "docs": ["Index of this log batch for tracking event sequences."], "type": "u32"}, {"name": "totalEvents", "docs": ["Number of events serialized in this log batch."], "type": "u32"}]}}, {"name": "OrderbookHeader", "docs": ["Fixed metadata prefix for a Phoenix Eternal orderbook account.", "The large recent-trade buffer and dynamic orderbook bytes follow this prefix and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "marketStatus", "type": "u8"}, {"name": "baseLotsDecimals", "type": "i8"}, {"name": "padding0", "type": {"array": ["u8", 6]}}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "assetId", "type": "u32"}, {"name": "assetIdPadding", "type": {"array": ["u8", 4]}}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "tickSizeInQuoteLotsPerBaseLot", "type": "u64"}, {"name": "orderSequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "tradeSequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "defaultTakerFeeMicro", "type": "u32"}, {"name": "defaultMakerFeeMicro", "type": "i32"}, {"name": "totalMakerQuoteLotFees", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "totalTakerQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}]}}, {"name": "SplineCollectionHeader", "docs": ["Fixed SplineCollectionHeader prefix for a Phoenix Eternal spline collection account.", "Dynamic spline map bytes follow this header and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "market", "type": "pubkey"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "numSplines", "type": "u32"}, {"name": "numActive", "type": "u32"}, {"name": "padding", "type": {"array": ["u8", 32]}}]}}, {"name": "TraderState", "docs": ["Fixed trader state embedded in the Phoenix Eternal DynamicTraderHeader."], "type": {"kind": "struct", "fields": [{"name": "quoteLotCollateral", "type": "i64"}, {"name": "flags", "type": "u32"}, {"name": "padding", "type": {"array": ["u8", 1]}}, {"name": "globalPositionSequenceNumber", "type": "u8"}, {"name": "makerFeeOverrideMultiplier", "type": "i8"}, {"name": "takerFeeOverrideMultiplier", "type": "i8"}]}}, {"name": "Trader", "docs": ["Fixed DynamicTraderHeader prefix for a Phoenix Eternal trader account.", "Dynamic position map bytes follow this header and are left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "key", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "traderState", "type": {"defined": {"name": "TraderState"}}}, {"name": "padding0", "type": {"array": ["u8", 4]}}, {"name": "withdrawQueueNode", "type": "u32"}, {"name": "maxPositions", "type": "u32"}, {"name": "traderPreferenceBits", "type": "u32"}, {"name": "positionAuthority", "type": "pubkey"}, {"name": "numMarketsWithSplines", "type": "u16"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "padding1", "type": {"array": ["u8", 4]}}, {"name": "lastDepositSlot", "type": "u64"}, {"name": "conditionalOrderBits", "type": {"array": ["u8", 24]}}]}}, {"name": "ActiveTraderBufferHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full active trader buffer layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "ActiveTraderBufferArenaHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full active trader buffer arena layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalTraderIndexHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full global trader index layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalTraderIndexArenaHeader", "docs": ["Discriminator-only account definition for viewer compatibility.", "The full global trader index arena layout is not modeled in this IDL."], "type": {"kind": "struct", "fields": []}}, {"name": "GlobalConfiguration", "type": {"kind": "struct", "fields": [{"name": "accountKey", "type": "pubkey"}, {"name": "rootAuthority", "type": "pubkey"}, {"name": "riskAuthority", "type": "pubkey"}, {"name": "marketAuthority", "type": "pubkey"}, {"name": "oracleAuthority", "type": "pubkey"}, {"name": "successorAuthority", "type": "pubkey"}, {"name": "adlAuthority", "type": "pubkey"}, {"name": "cancelAuthority", "type": "pubkey"}, {"name": "backstopAuthority", "type": "pubkey"}, {"name": "canonicalTokenMintKey", "type": "pubkey"}, {"name": "globalVaultKey", "type": "pubkey"}, {"name": "perpAssetMapKey", "type": "pubkey"}, {"name": "globalTraderIndexHeaderKey", "type": "pubkey"}, {"name": "activeTraderBufferHeaderKey", "type": "pubkey"}, {"name": "totalQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "unclaimedQuoteLotFees", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "withdrawQueueKey", "type": "pubkey"}, {"name": "exchangeStatus", "type": "u8"}, {"name": "quoteDecimals", "type": "u8"}, {"name": "withdrawalMarginFactorBps", "type": "u16"}, {"name": "padding0", "type": {"array": ["u8", 4]}}, {"name": "padding1", "type": {"array": ["u64", 32]}}, {"name": "padding2", "type": {"array": ["u64", 32]}}, {"name": "padding3", "type": {"array": ["u64", 32]}}, {"name": "padding4", "type": {"array": ["u64", 32]}}, {"name": "padding5", "type": {"array": ["u64", 32]}}, {"name": "padding6", "type": {"array": ["u64", 32]}}, {"name": "padding7", "type": {"array": ["u64", 32]}}, {"name": "padding8", "type": {"array": ["u64", 32]}}]}}, {"name": "SequenceNumber", "docs": ["Pair of sequence number and last update slot."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "lastUpdateSlot", "type": "u64"}]}}, {"name": "PerpAssetMetadata", "docs": ["Opaque fixed-size zero-copy PerpAssetMetadata payload stored inside PerpAssetMap."], "type": {"kind": "struct", "fields": [{"name": "raw", "type": {"array": ["u8", 1568]}}]}}, {"name": "PerpAssetMapEntry", "docs": ["One fixed slot in the PerpAssetMap ShortMapV2 backing array."], "type": {"kind": "struct", "fields": [{"name": "key", "type": {"defined": {"name": "Symbol"}}}, {"name": "value", "type": {"defined": {"name": "PerpAssetMetadata"}}}]}}, {"name": "PerpAssetMetadataShortMapV2", "docs": ["Zero-copy ShortMapV2 layout."], "type": {"kind": "struct", "fields": [{"name": "slotsUsed", "type": "u32"}, {"name": "tombstones", "type": "u32"}, {"name": "capacity", "type": "u64"}, {"name": "data", "type": {"array": [{"defined": {"name": "PerpAssetMapEntry"}}, 1024]}}]}}, {"name": "PerpAssetMap", "docs": ["Canonical Eternal perpetual asset map account."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "numAssets", "type": "u16"}, {"name": "padding0", "type": {"array": ["u8", 6]}}, {"name": "metadatas", "type": {"defined": {"name": "PerpAssetMetadataShortMapV2"}}}]}}, {"name": "WithdrawThrottle", "docs": ["Fixed WithdrawThrottle payload embedded in WithdrawQueueHeader."], "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "remainingBudget", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "replenishAmountPerSlot", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "lastUpdateSlot", "type": "u64"}]}}, {"name": "WithdrawQueueHeader", "docs": ["Fixed WithdrawQueueHeader prefix for the Phoenix Eternal withdraw queue account.", "The deque of pending withdrawal requests follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "withdrawThrottle", "type": {"defined": {"name": "WithdrawThrottle"}}}, {"name": "totalQueuedAmount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "withdrawalFee", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "enqueueingFee", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "reserved", "type": {"array": ["u64", 5]}}]}}, {"name": "PermissionAccount", "docs": ["Fixed Phoenix Eternal permission delegation account layout."], "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "user", "type": "pubkey"}, {"name": "bump", "type": "u8"}, {"name": "padding1", "type": {"array": ["u8", 7]}}, {"name": "permission", "type": "u64"}, {"name": "expiresAtTimestamp", "type": "i64"}, {"name": "numSignerActionsRemaining", "type": "i64"}, {"name": "padding2", "type": {"array": ["u8", 64]}}]}}, {"name": "StopLoss", "docs": ["One fixed trigger order slot stored inside the StopLosses account."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "slot", "type": "u64"}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "flags", "type": "u8"}, {"name": "padding", "type": {"array": ["u8", 6]}}, {"name": "padding2", "type": {"array": ["u8", 32]}}]}}, {"name": "StopLosses", "docs": ["Fixed Phoenix Eternal StopLosses account layout."], "type": {"kind": "struct", "fields": [{"name": "stopLosses", "type": {"array": [{"defined": {"name": "StopLoss"}}, 2]}}, {"name": "initialized", "type": "u64"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "fundingKey", "type": "pubkey"}, {"name": "traderKey", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "assetIdPadding", "type": {"array": ["u8", 4]}}, {"name": "padding", "type": {"array": ["u64", 8]}}]}}, {"name": "EscrowHeader", "docs": ["Fixed EscrowHeader prefix for a Phoenix Eternal escrow account.", "The dynamic escrow request list follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "funderKey", "type": "pubkey"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "len", "type": "u64"}, {"name": "capacity", "type": "u64"}]}}, {"name": "ConditionalOrderHeader", "docs": ["Fixed ConditionalOrderHeader prefix for a Phoenix Eternal conditional orders account.", "The dynamic conditional order array follows this header and is left as trailing account data by generic decoders."], "type": {"kind": "struct", "fields": [{"name": "traderKey", "type": "pubkey"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "sequenceNumber", "type": {"defined": {"name": "SequenceNumber"}}}, {"name": "len", "type": "u8"}, {"name": "capacity", "type": "u8"}, {"name": "padding", "type": {"array": ["u8", 6]}}]}}, {"name": "RevokePermissionParams", "type": {"kind": "struct", "fields": [{"name": "permissionFlagsToRevoke", "type": "u64"}]}}, {"name": "SetPermissionInstruction", "type": {"kind": "struct", "fields": [{"name": "permission", "type": "u64"}, {"name": "expiresAtTimestamp", "type": {"option": "i64"}}, {"name": "allowedSignerActions", "type": {"option": "i64"}}]}}, {"name": "TraderCapabilityToggleTarget", "type": {"kind": "enum", "variants": [{"name": "PlaceLimitOrder"}, {"name": "PlaceMarketOrder"}, {"name": "RiskIncreasingTrade"}, {"name": "RiskReducingTrade"}, {"name": "DepositCollateral"}, {"name": "WithdrawCollateral"}]}}, {"name": "TraderCapabilityToggle", "type": {"kind": "struct", "fields": [{"name": "target", "type": {"defined": {"name": "TraderCapabilityToggleTarget"}}}, {"name": "enable", "type": "bool"}]}}, {"name": "TraderCapabilityUpdate", "type": {"kind": "struct", "fields": [{"name": "toggles", "type": {"vec": {"defined": {"name": "TraderCapabilityToggle"}}}}]}}, {"name": "TransferCollateralInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "TransferCollateralChildToParentInstruction", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "UncrossCrankParams", "type": {"kind": "struct", "fields": [{"name": "matchLimit", "type": "u64"}]}}, {"name": "ClearExpiredOrdersParams", "type": {"kind": "struct", "fields": [{"name": "maxOrdersPerSide", "type": {"option": "u64"}}]}}, {"name": "TriggerOrderParams", "docs": ["Trigger and execution parameters for one half of a conditional order."], "type": {"kind": "struct", "fields": [{"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}, {"name": "triggerPrice", "type": "u64"}, {"name": "executionPrice", "type": "u64"}]}}, {"name": "CreateConditionalOrdersAccountInstruction", "type": {"kind": "struct", "fields": [{"name": "capacity", "type": "u8"}]}}, {"name": "PlacePositionConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u32"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "sizeBaseLots", "type": {"option": "u64"}}, {"name": "sizePercent", "type": {"option": "u8"}}]}}, {"name": "CancelConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "index", "type": "u8"}, {"name": "disableFirst", "type": "bool"}, {"name": "disableSecond", "type": "bool"}]}}, {"name": "PlaceAttachedConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "orderId", "type": {"defined": {"name": "FIFOOrderId"}}}, {"name": "assetId", "type": "u32"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}]}}, {"name": "PlaceLimitOrderWithConditionalsInstruction", "type": {"kind": "struct", "fields": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}, {"name": "slot", "type": "u64"}, {"name": "greaterTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}, {"name": "lessTriggerOrder", "type": {"option": {"defined": {"name": "TriggerOrderParams"}}}}]}}, {"name": "ExecuteConditionalOrderInstruction", "type": {"kind": "struct", "fields": [{"name": "index", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "OffChainMarketEvent", "docs": ["Borsh payload of the Log instruction after the 8-byte instruction discriminator."], "type": {"kind": "struct", "fields": [{"name": "batchIndex", "type": "u32"}, {"name": "events", "type": {"vec": {"defined": {"name": "MarketEvent"}}}}]}}, {"name": "OffChainMarketEventLengths", "docs": ["Borsh payload of the LogEventLengths instruction after the 8-byte instruction discriminator."], "type": {"kind": "struct", "fields": [{"name": "batchIndex", "type": "u32"}, {"name": "lengths", "type": {"vec": "u16"}}]}}, {"name": "SignedBaseLots", "docs": ["Signed quantity wrapper representing discrete base lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedQuoteLots", "docs": ["Signed quantity wrapper representing discrete quote lots."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedFeeRateMicro", "docs": ["Signed fee rate in millionths."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i32"}]}}, {"name": "SignedQuoteLotsPerBaseLot", "docs": ["Signed quote lots per base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "SignedQuoteLotsPerBaseLotUpcasted", "docs": ["Upcasted signed quote lots per base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i128"}]}}, {"name": "SignedTicks", "docs": ["Signed discrete tick offset."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "i64"}]}}, {"name": "FundingRateUnitInSeconds", "docs": ["Funding time unit measured in seconds."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "BaseLotsU32", "docs": ["u32-backed base lot wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u32"}]}}, {"name": "BaseLotsPerTickU32", "docs": ["u32-backed spline liquidity density wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u32"}]}}, {"name": "BasisPoints", "docs": ["Basis points wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "Constant", "docs": ["Dimensionless constant wrapper."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OptionalNonZeroU64", "docs": ["Zero represents None; non-zero represents Some(value)."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "OptionalFIFOOrderId", "type": {"kind": "struct", "fields": [{"name": "priceInTicks", "type": {"defined": {"name": "Ticks"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "TraderCapabilityFlags", "type": {"kind": "struct", "fields": [{"name": "bits", "type": "u32"}]}}, {"name": "MarketStatus", "type": {"kind": "enum", "variants": [{"name": "Uninitialized"}, {"name": "Active"}, {"name": "PostOnly"}, {"name": "Paused"}, {"name": "Closed"}, {"name": "Tombstoned"}]}}, {"name": "CommodityMarketState", "type": {"kind": "enum", "variants": [{"name": "Active"}, {"name": "AfterHours"}, {"name": "Reopen"}]}}, {"name": "AuthorityType", "type": {"kind": "enum", "variants": [{"name": "Root"}, {"name": "Risk"}, {"name": "Market"}, {"name": "Oracle"}, {"name": "Cancel"}, {"name": "Backstop"}, {"name": "ADL"}]}}, {"name": "ValidationRule", "type": {"kind": "enum", "variants": [{"name": "Ignore"}, {"name": "Require"}, {"name": "Forbid"}]}}, {"name": "LeverageTier", "type": {"kind": "struct", "fields": [{"name": "upperBoundSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "maxLeverage", "type": {"defined": {"name": "Constant"}}}, {"name": "limitOrderRiskFactor", "type": {"defined": {"name": "BasisPoints"}}}]}}, {"name": "LeverageTiers", "type": {"kind": "struct", "fields": [{"name": "tiers", "type": {"array": [{"defined": {"name": "LeverageTier"}}, 4]}}]}}, {"name": "PositionSizeLimits", "type": {"kind": "struct", "fields": [{"name": "long", "type": "u32"}, {"name": "short", "type": "u32"}]}}, {"name": "PositionSizeLimit", "type": {"kind": "enum", "variants": [{"name": "Disabled"}, {"name": "Limit", "fields": [{"name": "value", "type": {"defined": {"name": "PositionSizeLimits"}}}]}]}}, {"name": "TickRegion", "type": {"kind": "struct", "fields": [{"name": "startOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "endOffset", "type": {"defined": {"name": "Ticks"}}}, {"name": "density", "type": {"defined": {"name": "BaseLotsPerTickU32"}}}, {"name": "topLevelHiddenTakeSize", "type": {"defined": {"name": "BaseLotsPerTickU32"}}}, {"name": "totalSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLotsU32"}}}, {"name": "hiddenFilledSize", "type": {"defined": {"name": "BaseLotsU32"}}}, {"name": "lifespan", "type": "u64"}]}}, {"name": "OrderRejectionReason", "type": {"kind": "enum", "variants": [{"name": "TooManyLimitOrders"}, {"name": "PostOnlyCross"}, {"name": "InvalidOrderPacket"}, {"name": "TiFInvalid"}, {"name": "OutsideExecutionPriceBand"}]}}, {"name": "OrderResidualDiscardReason", "type": {"kind": "enum", "variants": [{"name": "OutsideExecutionPriceBand"}]}}, {"name": "OrderModificationReason", "type": {"kind": "enum", "variants": [{"name": "UserRequested"}, {"name": "AuthorityForced"}, {"name": "SelfTradeCancelProvide"}, {"name": "SelfTradeDecrementTake"}, {"name": "Expired"}, {"name": "ReduceOnlyInvalidated"}, {"name": "RiskCapacityExceeded"}, {"name": "BookCapacityEvicted"}, {"name": "Tombstone"}, {"name": "TopOfBookOutOfBounds"}, {"name": "StopLossCascade"}]}}, {"name": "ConditionalOrderPingStateSnapshot", "type": {"kind": "struct", "fields": [{"name": "value", "type": "u8"}]}}, {"name": "ConditionalOrderPingSnapshot", "type": {"kind": "struct", "fields": [{"name": "state", "type": {"defined": {"name": "ConditionalOrderPingStateSnapshot"}}}, {"name": "attachedOrderId", "type": {"defined": {"name": "OptionalFIFOOrderId"}}}, {"name": "maxSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "fillableSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "coPositionSequenceNumber", "type": {"option": "u8"}}]}}, {"name": "MarkPriceConfig", "type": {"kind": "struct", "fields": [{"name": "emaPeriodSlots", "type": "u64"}, {"name": "emaDiffRadius", "type": "u64"}, {"name": "bookPriceRadius", "type": "u64"}, {"name": "commoditiesAfterHoursRadius", "type": "u64"}, {"name": "spotPriceWeight", "type": "u64"}, {"name": "bookPriceWeight", "type": "u64"}, {"name": "perpPriceWeight", "type": "u64"}, {"name": "spotPriceStaleThreshold", "type": "u64"}, {"name": "bookPriceStaleThreshold", "type": "u64"}, {"name": "perpPriceStaleThreshold", "type": "u64"}, {"name": "riskActionPriceValidityRules", "type": {"array": [{"array": [{"array": [{"defined": {"name": "ValidationRule"}}, 8]}, 4]}, 8]}}, {"name": "oracleDivergenceRadius", "type": "u16"}, {"name": "minOracleResponses", "type": "u8"}, {"name": "commoditiesAfterHoursRadiusBps", "type": "u64"}]}}, {"name": "WithdrawConfig", "type": {"kind": "struct", "fields": [{"name": "depositCooldownPeriodInSlots", "type": "u64"}, {"name": "withdrawalFee", "type": "u64"}, {"name": "enqueueingFee", "type": "u64"}]}}, {"name": "WithdrawRateLimitConfig", "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": "u64"}, {"name": "replenishAmountPerSlot", "type": "u64"}]}}, {"name": "FundingConfig", "type": {"kind": "struct", "fields": [{"name": "fundingIntervalSeconds", "type": "u64"}, {"name": "fundingPeriodSeconds", "type": "u64"}, {"name": "maxFundingRate", "type": "i64"}]}}, {"name": "CommodityMarketStateConfig", "type": {"kind": "struct", "fields": [{"name": "marketState", "type": {"defined": {"name": "CommodityMarketState"}}}, {"name": "isCommodity", "type": "bool"}, {"name": "lastKnownIndexPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "lastIndexExpiryTimestamp", "type": "u64"}, {"name": "commoditiesAfterHoursRadius", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "MarketFeeConfig", "type": {"kind": "struct", "fields": [{"name": "defaultTakerFeeMicro", "type": "u32"}, {"name": "defaultMakerFeeMicro", "type": "i32"}]}}, {"name": "AdminParameterUpdateKind", "type": {"kind": "enum", "variants": [{"name": "CancelRiskFactor", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "IsolatedOnly", "kind": "struct", "fields": [{"name": "previous", "type": "bool"}, {"name": "new", "type": "bool"}]}, {"name": "LeverageTiers", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "LeverageTiers"}}}, {"name": "new", "type": {"defined": {"name": "LeverageTiers"}}}]}, {"name": "MarkPriceParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "MarkPriceConfig"}}}, {"name": "new", "type": {"defined": {"name": "MarkPriceConfig"}}}]}, {"name": "OpenInterestCap", "kind": "struct", "fields": [{"name": "previous", "type": "u64"}, {"name": "new", "type": "u64"}]}, {"name": "UpnlRiskFactor", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "UpnlRiskFactorForWithdrawals", "kind": "struct", "fields": [{"name": "previous", "type": "u16"}, {"name": "new", "type": "u16"}]}, {"name": "WithdrawParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "WithdrawConfig"}}}, {"name": "new", "type": {"defined": {"name": "WithdrawConfig"}}}]}, {"name": "WithdrawRateLimits", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "WithdrawRateLimitConfig"}}}, {"name": "new", "type": {"defined": {"name": "WithdrawRateLimitConfig"}}}]}, {"name": "TraderCapability", "kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "previousFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}]}, {"name": "FundingParameters", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "FundingConfig"}}}, {"name": "new", "type": {"defined": {"name": "FundingConfig"}}}]}, {"name": "MarketFees", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "MarketFeeConfig"}}}, {"name": "new", "type": {"defined": {"name": "MarketFeeConfig"}}}]}, {"name": "OpenInterestAdjustment", "kind": "struct", "fields": [{"name": "previousOpenInterest", "type": "u64"}, {"name": "newOpenInterest", "type": "u64"}]}, {"name": "CommodityMarketState", "kind": "struct", "fields": [{"name": "previous", "type": {"defined": {"name": "CommodityMarketStateConfig"}}}, {"name": "new", "type": {"defined": {"name": "CommodityMarketStateConfig"}}}]}]}}, {"name": "EscrowAction", "type": {"kind": "enum", "variants": [{"name": "Noop", "kind": "struct", "fields": [{"name": "padding0", "type": {"array": ["u8", 8]}}, {"name": "padding1", "type": {"array": ["u8", 128]}}]}, {"name": "Cash", "kind": "struct", "fields": [{"name": "amount", "type": "u64"}, {"name": "padding0", "type": {"array": ["u8", 128]}}]}]}}, {"name": "EscrowRequestCancelReason", "type": {"kind": "enum", "variants": [{"name": "Expiration"}, {"name": "CancelledBySender"}, {"name": "CancelledByReceiver"}]}}, {"name": "MarketEvent", "docs": ["Borsh enum emitted by Eternal Log instructions. Variant discriminators are one-byte ordinals."], "type": {"kind": "enum", "variants": [{"name": "SlotContext", "fields": [{"name": "value", "type": {"defined": {"name": "SlotContextEvent"}}}]}, {"name": "Header", "fields": [{"name": "value", "type": {"defined": {"name": "MarketEventHeader"}}}]}, {"name": "OrderPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "OrderPlacedEvent"}}}]}, {"name": "OrderFilled", "fields": [{"name": "value", "type": {"defined": {"name": "OrderFilledEvent"}}}]}, {"name": "OrderRejected", "fields": [{"name": "value", "type": {"defined": {"name": "OrderRejectedEvent"}}}]}, {"name": "SplineFilled", "fields": [{"name": "value", "type": {"defined": {"name": "SplineFilledEvent"}}}]}, {"name": "TradeSummary", "fields": [{"name": "value", "type": {"defined": {"name": "TradeSummaryEvent"}}}]}, {"name": "OrderModified", "fields": [{"name": "value", "type": {"defined": {"name": "OrderModifiedEvent"}}}]}, {"name": "MarketSummary", "fields": [{"name": "value", "type": {"defined": {"name": "MarketSummaryEvent"}}}]}, {"name": "TraderRegistered", "fields": [{"name": "value", "type": {"defined": {"name": "TraderRegisteredEvent"}}}]}, {"name": "TraderCollateralTransferred", "fields": [{"name": "value", "type": {"defined": {"name": "TraderCollateralTransferredEvent"}}}]}, {"name": "TraderActivated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderActivatedEvent"}}}]}, {"name": "TraderDeactivated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderDeactivatedEvent"}}}]}, {"name": "TraderFundsDeposited", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsDepositedEvent"}}}]}, {"name": "TraderFundsWithdrawn", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderFundsWithdrawnEnqueued", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderFundsWithdrawnDropped", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnEvent"}}}]}, {"name": "TraderWithdrawCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderWithdrawCancelledEvent"}}}]}, {"name": "TraderFundingSettled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundingSettledEvent"}}}]}, {"name": "SplineRegistered", "fields": [{"name": "value", "type": {"defined": {"name": "SplineRegisteredEvent"}}}]}, {"name": "SplineActivated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineActivatedEvent"}}}]}, {"name": "SplineDeactivated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineDeactivatedEvent"}}}]}, {"name": "SplinePriceUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePriceUpdatedEvent"}}}]}, {"name": "SplineParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplineParametersUpdatedEvent"}}}]}, {"name": "MarketAdded", "fields": [{"name": "value", "type": {"defined": {"name": "MarketAddedEvent"}}}]}, {"name": "MarketStatusChanged", "fields": [{"name": "value", "type": {"defined": {"name": "MarketStatusChangedEvent"}}}]}, {"name": "MarketParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "MarketParametersUpdatedEvent"}}}]}, {"name": "FundingParametersUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "FundingParametersUpdatedEvent"}}}]}, {"name": "FeesClaimed", "fields": [{"name": "value", "type": {"defined": {"name": "FeesClaimedEvent"}}}]}, {"name": "PricesUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "PricesUpdatedEvent"}}}]}, {"name": "LiquidationTransferSummary", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationTransferSummaryEvent"}}}]}, {"name": "LiquidationTransfer", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationTransferEvent"}}}]}, {"name": "Liquidation", "fields": [{"name": "value", "type": {"defined": {"name": "LiquidationEvent"}}}]}, {"name": "CloseMatchedPositions", "fields": [{"name": "value", "type": {"defined": {"name": "CloseMatchedPositionsEvent"}}}]}, {"name": "NameSuccessor", "fields": [{"name": "value", "type": {"defined": {"name": "NameSuccessorEvent"}}}]}, {"name": "ClaimAuthority", "fields": [{"name": "value", "type": {"defined": {"name": "ClaimAuthorityEvent"}}}]}, {"name": "WithdrawStateTransition", "fields": [{"name": "value", "type": {"defined": {"name": "WithdrawStateTransitionEvent"}}}]}, {"name": "PnL", "fields": [{"name": "value", "type": {"defined": {"name": "PnLEvent"}}}]}, {"name": "StopLossPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossPlacedEvent"}}}]}, {"name": "StopLossCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossCancelledEvent"}}}]}, {"name": "StopLossExecuted", "fields": [{"name": "value", "type": {"defined": {"name": "StopLossExecutedEvent"}}}]}, {"name": "ExchangeStatusChanged", "fields": [{"name": "value", "type": {"defined": {"name": "ExchangeStatusChangedEvent"}}}]}, {"name": "TraderCapabilitiesEnabled", "fields": [{"name": "value", "type": {"defined": {"name": "TraderCapabilitiesEnabledEvent"}}}]}, {"name": "TraderFundsWithdrawnFeePayment", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFundsWithdrawnFeePaymentEvent"}}}]}, {"name": "OrderPacket", "fields": [{"name": "value", "type": {"defined": {"name": "OrderPacketEvent"}}}]}, {"name": "SetPermission", "fields": [{"name": "value", "type": {"defined": {"name": "SetPermissionEvent"}}}]}, {"name": "AuthorityChanged", "fields": [{"name": "value", "type": {"defined": {"name": "AuthorityChangedEvent"}}}]}, {"name": "TraderDelegated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderDelegatedEvent"}}}]}, {"name": "AdminParameterUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "AdminParameterUpdatedEvent"}}}]}, {"name": "TraderFeesUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "TraderFeesUpdatedEvent"}}}]}, {"name": "MarketClosed", "fields": [{"name": "value", "type": {"defined": {"name": "MarketClosedEvent"}}}]}, {"name": "MarketDeleted", "fields": [{"name": "value", "type": {"defined": {"name": "MarketDeletedEvent"}}}]}, {"name": "EscrowAccountCreated", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowAccountCreatedEvent"}}}]}, {"name": "EscrowRequestCreated", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestCreatedEvent"}}}]}, {"name": "EscrowRequestAccepted", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestAcceptedEvent"}}}]}, {"name": "EscrowRequestCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "EscrowRequestCancelledEvent"}}}]}, {"name": "SplinePriceUpdatedWithOrdering", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePriceUpdatedWithOrderingEvent"}}}]}, {"name": "SplineParametersUpdatedWithOrdering", "fields": [{"name": "value", "type": {"defined": {"name": "SplineParametersUpdatedWithOrderingEvent"}}}]}, {"name": "TriggerOrderPlaced", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderPlacedEvent"}}}]}, {"name": "TriggerOrderCancelled", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderCancelledEvent"}}}]}, {"name": "TriggerOrderExecuted", "fields": [{"name": "value", "type": {"defined": {"name": "TriggerOrderExecutedEvent"}}}]}, {"name": "PingInvalidated", "fields": [{"name": "value", "type": {"defined": {"name": "PingInvalidatedEvent"}}}]}, {"name": "PingActivated", "fields": [{"name": "value", "type": {"defined": {"name": "PingActivatedEvent"}}}]}, {"name": "SplinePositionLimitsConfigUpdated", "fields": [{"name": "value", "type": {"defined": {"name": "SplinePositionLimitsConfigUpdatedEvent"}}}]}, {"name": "MarketTombstoned", "fields": [{"name": "value", "type": {"defined": {"name": "MarketTombstonedEvent"}}}]}, {"name": "ShutdownClosePositions", "fields": [{"name": "value", "type": {"defined": {"name": "ShutdownClosePositionsEvent"}}}]}, {"name": "OrderResidualDiscarded", "fields": [{"name": "value", "type": {"defined": {"name": "OrderResidualDiscardedEvent"}}}]}]}}, {"name": "SlotContextEvent", "type": {"kind": "struct", "fields": [{"name": "timestamp", "type": "u64"}, {"name": "slot", "type": "u64"}]}}, {"name": "MarketEventHeader", "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "tickSize", "type": "u32"}, {"name": "baseLotDecimals", "type": "i8"}, {"name": "quoteLotDecimals", "type": "u8"}, {"name": "signer", "type": "pubkey"}, {"name": "traderAccount", "type": "pubkey"}]}}, {"name": "OrderPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "orderId", "type": "u32"}, {"name": "orderFlags", "type": {"defined": {"name": "OrderFlags"}}}, {"name": "orderSequenceNumber", "type": "u64"}, {"name": "prevOrderSequenceNumberSlot", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "quantity", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "lastValidSlot", "type": {"defined": {"name": "OptionalNonZeroU64"}}}, {"name": "initialSlot", "type": "u64"}]}}, {"name": "OrderPacketEvent", "type": {"kind": "struct", "fields": [{"name": "orderPacket", "type": {"defined": {"name": "OrderPacket"}}}, {"name": "trader", "type": "pubkey"}, {"name": "nextOrderSequenceNumber", "type": "u64"}]}}, {"name": "OrderRejectedEvent", "type": {"kind": "struct", "fields": [{"name": "orderIndex", "type": "u32"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "numBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"array": ["u8", 32]}}]}}, {"name": "OrderResidualDiscardedEvent", "type": {"kind": "struct", "fields": [{"name": "clientOrderId", "type": {"array": ["u8", 16]}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "baseLotsDiscarded", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"defined": {"name": "OrderResidualDiscardReason"}}}]}}, {"name": "OrderFilledEvent", "type": {"kind": "struct", "fields": [{"name": "orderSequenceNumber", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "quantityRemaining", "type": {"defined": {"name": "BaseLots"}}}, {"name": "maker", "type": "pubkey"}, {"name": "makerFeeRate", "type": {"defined": {"name": "SignedFeeRateMicro"}}}, {"name": "makerBaseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "makerVirtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerQuoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerCumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "SplineFilledEvent", "type": {"kind": "struct", "fields": [{"name": "splineSequenceNumber", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "maker", "type": "pubkey"}, {"name": "makerFeeRate", "type": {"defined": {"name": "SignedFeeRateMicro"}}}, {"name": "makerBaseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "makerVirtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerQuoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "makerCumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "OrderModifiedEvent", "type": {"kind": "struct", "fields": [{"name": "orderSequenceNumber", "type": "u64"}, {"name": "price", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsReleased", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "quoteLotsReleased", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "baseLotsRemaining", "type": {"defined": {"name": "BaseLots"}}}, {"name": "reason", "type": {"defined": {"name": "OrderModificationReason"}}}]}}, {"name": "TradeSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "tradeSequenceNumber", "type": "u64"}, {"name": "prevTradeSequenceNumberSlot", "type": "u64"}, {"name": "side", "type": {"defined": {"name": "Side"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "feeInQuoteLots", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "baseLotPosition", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotPosition", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "quoteLotCollateral", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "cumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}]}}, {"name": "MarketSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "openInterest", "type": {"defined": {"name": "BaseLots"}}}, {"name": "totalMakerQuoteLotFees", "type": {"option": {"defined": {"name": "SignedQuoteLots"}}}}, {"name": "totalTakerQuoteLotFees", "type": {"option": {"defined": {"name": "QuoteLots"}}}}, {"name": "markPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "spotPrice", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "TraderRegisteredEvent", "type": {"kind": "struct", "fields": [{"name": "traderSequenceNumber", "type": "u64"}, {"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "maxPositions", "type": "u64"}, {"name": "traderPdaIndex", "type": "u8"}, {"name": "traderSubaccountIndex", "type": "u8"}]}}, {"name": "TraderDelegatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "oldPositionAuthority", "type": "pubkey"}, {"name": "newPositionAuthority", "type": "pubkey"}]}}, {"name": "TraderFeesUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "previousMakerFeeOverrideMultiplier", "type": "i8"}, {"name": "newMakerFeeOverrideMultiplier", "type": "i8"}, {"name": "previousTakerFeeOverrideMultiplier", "type": "i8"}, {"name": "newTakerFeeOverrideMultiplier", "type": "i8"}, {"name": "isHotTrader", "type": "bool"}]}}, {"name": "TraderCapabilitiesEnabledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "previousFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "globalTraderIndex", "type": "u32"}]}}, {"name": "TraderActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "globalTraderIndex", "type": "u32"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "TraderDeactivatedEvent", "type": {"kind": "struct", "fields": [{"name": "prevGlobalTraderIndex", "type": "u32"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "TraderFundsDepositedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "traderFlags", "type": {"defined": {"name": "TraderCapabilityFlags"}}}, {"name": "newCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnFeePaymentEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "fee", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderWithdrawCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderCollateralTransferredEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "srcTrader", "type": "pubkey"}, {"name": "srcTraderSequenceNumber", "type": "u64"}, {"name": "srcTraderPrevSequenceNumberSlot", "type": "u64"}, {"name": "srcTraderNewCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "dstTrader", "type": "pubkey"}, {"name": "dstTraderSequenceNumber", "type": "u64"}, {"name": "dstTraderPrevSequenceNumberSlot", "type": "u64"}, {"name": "dstTraderNewCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "TraderFundingSettledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "fundingPayment", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "cumulativeFundingSnapshot", "type": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}, {"name": "newCollateralBalance", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "SplineRegisteredEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "SplineActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}]}}, {"name": "SplineDeactivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "SplinePositionLimitsConfigUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "maxPositionSize", "type": {"option": {"defined": {"name": "PositionSizeLimit"}}}}, {"name": "prevMaxPositionSize", "type": {"defined": {"name": "PositionSizeLimit"}}}, {"name": "leverageDecreaseInBps", "type": {"option": "u32"}}, {"name": "prevLeverageDecreaseInBps", "type": "u32"}]}}, {"name": "SplinePriceUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "priceInTicks", "type": "u64"}, {"name": "userUpdateSlot", "type": "u64"}, {"name": "refreshRegions", "type": "bool"}]}}, {"name": "SplineParametersUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}, {"name": "bidRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "askRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}]}}, {"name": "SplinePriceUpdatedWithOrderingEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "priceInTicks", "type": "u64"}, {"name": "userUpdateSlot", "type": "u64"}, {"name": "refreshRegions", "type": "bool"}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}]}}, {"name": "SplineParametersUpdatedWithOrderingEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "authority", "type": "pubkey"}, {"name": "market", "type": "pubkey"}, {"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "midPrice", "type": "u64"}, {"name": "bidRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "askRegions", "type": {"array": [{"defined": {"name": "TickRegion"}}, 10]}}, {"name": "userSequenceNumber", "type": "u64"}, {"name": "clientOrderId", "type": {"array": ["u8", 16]}}]}}, {"name": "StopLossPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "placeSlot", "type": "u64"}, {"name": "fundingKey", "type": "pubkey"}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}]}}, {"name": "StopLossCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "StopLossExecutedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "executionDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "TriggerOrderPlacedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "executionPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSide", "type": {"defined": {"name": "Side"}}}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderKind", "type": {"defined": {"name": "StopLossOrderKind"}}}, {"name": "maxSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "fillableSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "filledSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "usePercent", "type": "bool"}, {"name": "percent", "type": "u8"}, {"name": "positionSequenceNumber", "type": "u8"}, {"name": "attachedOrderId", "type": {"defined": {"name": "OptionalFIFOOrderId"}}}]}}, {"name": "TriggerOrderCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}]}}, {"name": "TriggerOrderExecutedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "triggerDirection", "type": {"defined": {"name": "Direction"}}}, {"name": "orderSequenceNumber", "type": "u64"}]}}, {"name": "PingInvalidatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "pre", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "post", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "currentPositionSequenceNumber", "type": "u8"}]}}, {"name": "PingActivatedEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "prevSequenceNumberSlot", "type": "u64"}, {"name": "assetId", "type": "u64"}, {"name": "conditionalOrderIndex", "type": "u8"}, {"name": "pre", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "post", "type": {"defined": {"name": "ConditionalOrderPingSnapshot"}}}, {"name": "bookOrderRemainingBaseLots", "type": {"option": {"defined": {"name": "BaseLots"}}}}]}}, {"name": "MarketAddedEvent", "type": {"kind": "struct", "fields": []}}, {"name": "MarketStatusChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "newMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}]}}, {"name": "MarketClosedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "finalizedMarkPrice", "type": {"defined": {"name": "Ticks"}}}]}}, {"name": "MarketTombstonedEvent", "type": {"kind": "struct", "fields": [{"name": "previousMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}, {"name": "finalSequenceNumber", "type": "u64"}, {"name": "finalTradeSequenceNumber", "type": "u64"}, {"name": "finalOrderSequenceNumber", "type": "u64"}]}}, {"name": "ShutdownClosePositionsEvent", "type": {"kind": "struct", "fields": [{"name": "longTrader", "type": "pubkey"}, {"name": "shortTrader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "baseLotsClosed", "type": {"defined": {"name": "BaseLots"}}}, {"name": "settlementPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "tradeSequenceNumber", "type": "u64"}, {"name": "sequenceNumber", "type": "u64"}]}}, {"name": "MarketDeletedEvent", "type": {"kind": "struct", "fields": [{"name": "assetId", "type": "u32"}, {"name": "lamportsReclaimed", "type": "u64"}]}}, {"name": "ExchangeStatusChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousBits", "type": "u8"}, {"name": "newBits", "type": "u8"}, {"name": "authority", "type": "pubkey"}]}}, {"name": "MarketParametersUpdatedEvent", "type": {"kind": "struct", "fields": []}}, {"name": "FundingParametersUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "symbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "newFundingIntervalSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "newFundingPeriodSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "newMaxFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}]}}, {"name": "FeesClaimedEvent", "type": {"kind": "struct", "fields": [{"name": "amount", "type": "u64"}]}}, {"name": "SetPermissionEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "user", "type": "pubkey"}, {"name": "previousPermission", "type": "u64"}, {"name": "newPermission", "type": "u64"}, {"name": "previousExpiresAtTimestamp", "type": "i64"}, {"name": "newExpiresAtTimestamp", "type": "i64"}, {"name": "previousNumSignerActionsRemaining", "type": "i64"}, {"name": "newNumSignerActionsRemaining", "type": "i64"}, {"name": "created", "type": "bool"}]}}, {"name": "PricesUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "oracleSigner", "type": {"option": "pubkey"}}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "assetId", "type": "u32"}, {"name": "newBestBid", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newBestAsk", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newLastTrade", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newExchangeSpotPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newExchangePerpPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "newMidSpotDiffEmaTicks", "type": {"option": {"defined": {"name": "SignedTicks"}}}}, {"name": "newMarkPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "cumulativeFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}, {"name": "settledContribution", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}, {"name": "intervalAccumulator", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLotUpcasted"}}}}, {"name": "assetSequenceNumber", "type": "u64"}, {"name": "prevAssetSequenceNumberSlot", "type": "u64"}]}}, {"name": "LiquidationEvent", "type": {"kind": "struct", "fields": [{"name": "liquidator", "type": "pubkey"}, {"name": "liquidatedTrader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "liquidationSize", "type": {"defined": {"name": "BaseLots"}}}, {"name": "markPrice", "type": {"defined": {"name": "Ticks"}}}, {"name": "baseLotsFilled", "type": {"defined": {"name": "BaseLots"}}}, {"name": "quoteLotsFilled", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "positionClosed", "type": "bool"}]}}, {"name": "LiquidationTransferSummaryEvent", "type": {"kind": "struct", "fields": [{"name": "liquidatee", "type": "pubkey"}, {"name": "liquidator", "type": "pubkey"}, {"name": "totalTransfers", "type": "u32"}, {"name": "liquidateeCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "liquidatorCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "haircutCollected", "type": {"defined": {"name": "QuoteLots"}}}]}}, {"name": "LiquidationTransferEvent", "type": {"kind": "struct", "fields": [{"name": "liquidatee", "type": "pubkey"}, {"name": "liquidator", "type": "pubkey"}, {"name": "assetId", "type": "u64"}, {"name": "baseLotsTransferred", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotsTransferred", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "haircutRate", "type": "u16"}, {"name": "liquidateeCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "liquidatorCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "CloseMatchedPositionsEvent", "type": {"kind": "struct", "fields": [{"name": "caller", "type": "pubkey"}, {"name": "closedShort", "type": "pubkey"}, {"name": "closedLong", "type": "pubkey"}, {"name": "inProfitAccount", "type": "pubkey"}, {"name": "assetId", "type": "u64"}, {"name": "baseLotsClosed", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "atLossCloseValue", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "inProfitCloseValue", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "atLossCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "inProfitCollateralChange", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "NameSuccessorEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}]}}, {"name": "ClaimAuthorityEvent", "type": {"kind": "struct", "fields": [{"name": "previousAuthority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}]}}, {"name": "AuthorityChangedEvent", "type": {"kind": "struct", "fields": [{"name": "previousAuthority", "type": "pubkey"}, {"name": "newAuthority", "type": "pubkey"}, {"name": "authorityType", "type": {"defined": {"name": "AuthorityType"}}}]}}, {"name": "WithdrawStateTransitionEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "amount", "type": {"defined": {"name": "QuoteLots"}}}, {"name": "fromState", "type": "u8"}, {"name": "toState", "type": "u8"}, {"name": "reason", "type": "u8"}, {"name": "transitionCount", "type": "u16"}, {"name": "nodeIndex", "type": {"defined": {"name": "NodePointer"}}}]}}, {"name": "PnLEvent", "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "assetId", "type": "u32"}, {"name": "assetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "realizedPnl", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "fundingPayment", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "baseLotsBefore", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "baseLotsAfter", "type": {"defined": {"name": "SignedBaseLots"}}}, {"name": "virtualQuoteLotsBefore", "type": {"defined": {"name": "SignedQuoteLots"}}}, {"name": "virtualQuoteLotsAfter", "type": {"defined": {"name": "SignedQuoteLots"}}}]}}, {"name": "AdminParameterUpdatedEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "assetSymbol", "type": {"option": {"defined": {"name": "Symbol"}}}}, {"name": "assetId", "type": {"option": "u32"}}, {"name": "updateKind", "type": {"defined": {"name": "AdminParameterUpdateKind"}}}]}}, {"name": "EscrowAccountCreatedEvent", "type": {"kind": "struct", "fields": [{"name": "authority", "type": "pubkey"}, {"name": "capacity", "type": "u64"}]}}, {"name": "EscrowRequestCreatedEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "senderAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "senderPdaIndex", "type": "u8"}, {"name": "senderSubaccountIndex", "type": "u8"}, {"name": "receiverPdaIndex", "type": "u8"}, {"name": "receiverSubaccountIndex", "type": "u8"}, {"name": "expirationOffset", "type": "u32"}, {"name": "initialSlot", "type": "u64"}, {"name": "actions", "type": {"array": [{"defined": {"name": "EscrowAction"}}, 4]}}]}}, {"name": "EscrowRequestAcceptedEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}]}}, {"name": "EscrowRequestCancelledEvent", "type": {"kind": "struct", "fields": [{"name": "receiverAuthority", "type": "pubkey"}, {"name": "sequenceNumber", "type": "u64"}, {"name": "reason", "type": {"defined": {"name": "EscrowRequestCancelReason"}}}]}}, {"name": "ChangeMarketStatusParams", "docs": ["Borsh payload for changing an orderbook market status."], "type": {"kind": "struct", "fields": [{"name": "nextMarketStatus", "type": {"defined": {"name": "MarketStatus"}}}]}}, {"name": "AuthorizedForceCancelParams", "docs": ["Borsh payload for authority-driven force cancellation by remaining side size."], "type": {"kind": "struct", "fields": [{"name": "remainingBidBaseLots", "type": {"defined": {"name": "BaseLots"}}}, {"name": "remainingAskBaseLots", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "AuthorizedForceCancelByIdParams", "docs": ["Borsh payload for authority-driven force cancellation by order id."], "type": {"kind": "struct", "fields": [{"name": "bidOrderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}, {"name": "askOrderIds", "type": {"vec": {"defined": {"name": "CancelId"}}}}]}}, {"name": "CreateEscrowAccountParams", "docs": ["Borsh payload for creating an escrow account."], "type": {"kind": "struct", "fields": [{"name": "capacity", "type": "u64"}]}}, {"name": "EscrowParticipantMetadata", "docs": ["Trader PDA/subaccount metadata stored with escrow requests."], "type": {"kind": "struct", "fields": [{"name": "senderPdaIndex", "type": "u8"}, {"name": "senderSubaccountIndex", "type": "u8"}, {"name": "receiverPdaIndex", "type": "u8"}, {"name": "receiverSubaccountIndex", "type": "u8"}]}}, {"name": "CreateEscrowRequestParams", "docs": ["Borsh payload for creating an escrow request."], "type": {"kind": "struct", "fields": [{"name": "participantMetadata", "type": {"defined": {"name": "EscrowParticipantMetadata"}}}, {"name": "lastValidSlot", "type": {"option": "u64"}}, {"name": "actions", "type": {"vec": {"defined": {"name": "EscrowAction"}}}}]}}, {"name": "CancelEscrowRequestParams", "docs": ["Borsh payload for cancelling an escrow request by sequence number."], "type": {"kind": "struct", "fields": [{"name": "sequenceNumber", "type": "u64"}]}}, {"name": "UpsertEscrowRequestParams", "docs": ["Borsh payload for upserting an escrow request."], "type": {"kind": "struct", "fields": [{"name": "participantMetadata", "type": {"defined": {"name": "EscrowParticipantMetadata"}}}, {"name": "action", "type": {"defined": {"name": "EscrowAction"}}}]}}, {"name": "RegisterSplineParams", "docs": ["Empty Borsh payload for registering a spline trader."], "type": {"kind": "struct", "fields": []}}, {"name": "UpdateCommodityMarketStateInstruction", "docs": ["Borsh payload for updating commodity market state and mark price inputs."], "type": {"kind": "struct", "fields": [{"name": "marketState", "type": {"defined": {"name": "CommodityMarketState"}}}, {"name": "lastKnownIndexPrice", "type": {"option": {"defined": {"name": "Ticks"}}}}, {"name": "lastIndexExpiryTimestamp", "type": "u64"}, {"name": "emaPeriodSlots", "type": {"option": "u64"}}]}}, {"name": "UpdatePerpIsolatedOnlyInstruction", "docs": ["Borsh payload for updating isolated-only metadata for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "isolatedOnly", "type": "u8"}]}}, {"name": "UpdatePerpLeverageTiersInstruction", "docs": ["Borsh payload for updating leverage tiers for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "leverageTiers", "type": {"defined": {"name": "LeverageTiers"}}}]}}, {"name": "UpdatePerpOpenInterestCapInstruction", "docs": ["Borsh payload for updating open interest cap for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "openInterestCap", "type": {"defined": {"name": "BaseLots"}}}]}}, {"name": "UPnlRiskFactor", "docs": ["uPnL risk factor encoded as basis points."], "type": {"kind": "struct", "fields": [{"name": "inner", "type": "u64"}]}}, {"name": "UpdatePerpUPnlRiskFactorInstruction", "docs": ["Borsh payload for updating uPnL risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "upnlRiskFactor", "type": {"defined": {"name": "UPnlRiskFactor"}}}]}}, {"name": "UpdatePerpUPnlRiskFactorForWithdrawalsInstruction", "docs": ["Borsh payload for updating withdrawal uPnL risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "upnlRiskFactorForWithdrawals", "type": {"defined": {"name": "UPnlRiskFactor"}}}]}}, {"name": "UpdatePerpCancelRiskFactorInstruction", "docs": ["Borsh payload for updating cancel-order risk factor for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "cancelOrderRiskFactor", "type": "u16"}]}}, {"name": "UpdatePerpMarkPriceParametersInstruction", "docs": ["Borsh payload for updating mark price calculation parameters for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "emaPeriodSlots", "type": {"option": "u64"}}, {"name": "emaDiffRadius", "type": {"option": "u64"}}, {"name": "bookPriceRadius", "type": {"option": "u64"}}, {"name": "commoditiesAfterHoursRadiusBps", "type": {"option": "u64"}}, {"name": "adjustedExchangeSpotPriceWeight", "type": {"option": "u64"}}, {"name": "bookPriceWeight", "type": {"option": "u64"}}, {"name": "exchangePerpPriceWeight", "type": {"option": "u64"}}, {"name": "spotPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "bookPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "perpPriceStaleThreshold", "type": {"option": "u64"}}, {"name": "riskActionPriceValidityRules", "type": {"option": {"array": [{"array": [{"array": [{"defined": {"name": "ValidationRule"}}, 8]}, 4]}, 8]}}}, {"name": "oracleDivergenceRadius", "type": {"option": "u16"}}, {"name": "minOracleResponses", "type": {"option": "u8"}}]}}, {"name": "UpdateTraderFeesParams", "docs": ["Borsh payload for updating trader fee override multipliers."], "type": {"kind": "struct", "fields": [{"name": "makerFeeOverrideMultiplier", "type": "i8"}, {"name": "takerFeeOverrideMultiplier", "type": "i8"}]}}, {"name": "UpdateMarketFeesParams", "docs": ["Borsh payload for updating market default fees."], "type": {"kind": "struct", "fields": [{"name": "defaultTakerFeeMicro", "type": {"option": "u32"}}, {"name": "defaultMakerFeeMicro", "type": {"option": "i32"}}]}}, {"name": "UpdateWithdrawRateLimitsParams", "docs": ["Borsh payload for updating withdraw throttle rate limits."], "type": {"kind": "struct", "fields": [{"name": "maxBudget", "type": {"option": "u64"}}, {"name": "replenishAmountPerSlot", "type": {"option": "u64"}}]}}, {"name": "UpdateWithdrawParametersParams", "docs": ["Borsh payload for updating withdraw cooldown and fee parameters."], "type": {"kind": "struct", "fields": [{"name": "depositCooldownPeriodInSlots", "type": {"option": "u64"}}, {"name": "withdrawalFee", "type": {"option": "u64"}}, {"name": "enqueueingFee", "type": {"option": "u64"}}]}}, {"name": "UpdateAuthoritiesParams", "docs": ["Borsh payload for root authority updates to subordinate authorities."], "type": {"kind": "struct", "fields": [{"name": "newRiskAuthority", "type": {"option": "pubkey"}}, {"name": "newMarketAuthority", "type": {"option": "pubkey"}}, {"name": "newOracleAuthority", "type": {"option": "pubkey"}}, {"name": "newCancelAuthority", "type": {"option": "pubkey"}}, {"name": "newBackstopAuthority", "type": {"option": "pubkey"}}, {"name": "newAdlAuthority", "type": {"option": "pubkey"}}]}}, {"name": "ArenaType", "docs": ["Arena account family to initialize."], "type": {"kind": "enum", "variants": [{"name": "GlobalTraderIndex"}, {"name": "ActiveTraderBuffer"}]}}, {"name": "InitializeArenaParams", "docs": ["Borsh payload for initializing a new arena account."], "type": {"kind": "struct", "fields": [{"name": "arenaType", "type": {"defined": {"name": "ArenaType"}}}]}}, {"name": "RemoveOracleInstruction", "docs": ["Borsh payload for removing oracle public keys from the perp asset map."], "type": {"kind": "struct", "fields": [{"name": "oracleKeys", "type": {"vec": "pubkey"}}]}}, {"name": "RemoveAllOraclesInstruction", "docs": ["Borsh payload for removing all oracle keys for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}]}}, {"name": "UpdateFundingParametersInstruction", "docs": ["Borsh payload for updating funding parameters for a perp asset."], "type": {"kind": "struct", "fields": [{"name": "perpAssetSymbol", "type": {"defined": {"name": "Symbol"}}}, {"name": "fundingIntervalSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "fundingPeriodSeconds", "type": {"option": {"defined": {"name": "FundingRateUnitInSeconds"}}}}, {"name": "maxFundingRate", "type": {"option": {"defined": {"name": "SignedQuoteLotsPerBaseLot"}}}}]}}, {"name": "UpdateSplinePositionLimitsConfigParams", "docs": ["Borsh payload for updating spline position-size limits and leverage decrease configuration."], "type": {"kind": "struct", "fields": [{"name": "maxPositionSize", "type": {"option": {"defined": {"name": "PositionSizeLimit"}}}}, {"name": "leverageDecreaseInBps", "type": {"option": "u32"}}]}}, {"name": "TraderFundsWithdrawnEnqueuedEvent", "docs": ["Viewer-safe alias for MarketEvent::TraderFundsWithdrawnEnqueued.", "Uses the same payload layout as TraderFundsWithdrawnEvent."], "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}, {"name": "TraderFundsWithdrawnDroppedEvent", "docs": ["Viewer-safe alias for MarketEvent::TraderFundsWithdrawnDropped.", "Uses the same payload layout as TraderFundsWithdrawnEvent."], "type": {"kind": "struct", "fields": [{"name": "trader", "type": "pubkey"}, {"name": "authority", "type": "pubkey"}, {"name": "amount", "type": "u64"}, {"name": "traderSequenceNumber", "type": "u64"}, {"name": "traderPrevSequenceNumberSlot", "type": "u64"}, {"name": "postWithdrawalBudget", "type": "u64"}, {"name": "postQueueSize", "type": "u64"}, {"name": "totalQueuedAmount", "type": "u64"}, {"name": "withdrawQueueSequenceNumber", "type": "u64"}, {"name": "withdrawQueuePrevSequenceNumberSlot", "type": "u64"}]}}], "events": [{"name": "SlotContextEvent", "docs": ["MarketEvent::SlotContext Borsh variant 0.", "Payload type: SlotContextEvent."], "discriminator": [0]}, {"name": "MarketEventHeader", "docs": ["MarketEvent::Header Borsh variant 1.", "Payload type: MarketEventHeader."], "discriminator": [1]}, {"name": "OrderPlacedEvent", "docs": ["MarketEvent::OrderPlaced Borsh variant 2.", "Payload type: OrderPlacedEvent."], "discriminator": [2]}, {"name": "OrderFilledEvent", "docs": ["MarketEvent::OrderFilled Borsh variant 3.", "Payload type: OrderFilledEvent."], "discriminator": [3]}, {"name": "OrderRejectedEvent", "docs": ["MarketEvent::OrderRejected Borsh variant 4.", "Payload type: OrderRejectedEvent."], "discriminator": [4]}, {"name": "SplineFilledEvent", "docs": ["MarketEvent::SplineFilled Borsh variant 5.", "Payload type: SplineFilledEvent."], "discriminator": [5]}, {"name": "TradeSummaryEvent", "docs": ["MarketEvent::TradeSummary Borsh variant 6.", "Payload type: TradeSummaryEvent."], "discriminator": [6]}, {"name": "OrderModifiedEvent", "docs": ["MarketEvent::OrderModified Borsh variant 7.", "Payload type: OrderModifiedEvent."], "discriminator": [7]}, {"name": "MarketSummaryEvent", "docs": ["MarketEvent::MarketSummary Borsh variant 8.", "Payload type: MarketSummaryEvent."], "discriminator": [8]}, {"name": "TraderRegisteredEvent", "docs": ["MarketEvent::TraderRegistered Borsh variant 9.", "Payload type: TraderRegisteredEvent."], "discriminator": [9]}, {"name": "TraderCollateralTransferredEvent", "docs": ["MarketEvent::TraderCollateralTransferred Borsh variant 10.", "Payload type: TraderCollateralTransferredEvent."], "discriminator": [10]}, {"name": "TraderActivatedEvent", "docs": ["MarketEvent::TraderActivated Borsh variant 11.", "Payload type: TraderActivatedEvent."], "discriminator": [11]}, {"name": "TraderDeactivatedEvent", "docs": ["MarketEvent::TraderDeactivated Borsh variant 12.", "Payload type: TraderDeactivatedEvent."], "discriminator": [12]}, {"name": "TraderFundsDepositedEvent", "docs": ["MarketEvent::TraderFundsDeposited Borsh variant 13.", "Payload type: TraderFundsDepositedEvent."], "discriminator": [13]}, {"name": "TraderFundsWithdrawnEvent", "docs": ["MarketEvent::TraderFundsWithdrawn Borsh variant 14.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [14]}, {"name": "TraderFundsWithdrawnEnqueuedEvent", "docs": ["MarketEvent::TraderFundsWithdrawnEnqueued Borsh variant 15.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [15]}, {"name": "TraderFundsWithdrawnDroppedEvent", "docs": ["MarketEvent::TraderFundsWithdrawnDropped Borsh variant 16.", "Payload type: TraderFundsWithdrawnEvent."], "discriminator": [16]}, {"name": "TraderWithdrawCancelledEvent", "docs": ["MarketEvent::TraderWithdrawCancelled Borsh variant 17.", "Payload type: TraderWithdrawCancelledEvent."], "discriminator": [17]}, {"name": "TraderFundingSettledEvent", "docs": ["MarketEvent::TraderFundingSettled Borsh variant 18.", "Payload type: TraderFundingSettledEvent."], "discriminator": [18]}, {"name": "SplineRegisteredEvent", "docs": ["MarketEvent::SplineRegistered Borsh variant 19.", "Payload type: SplineRegisteredEvent."], "discriminator": [19]}, {"name": "SplineActivatedEvent", "docs": ["MarketEvent::SplineActivated Borsh variant 20.", "Payload type: SplineActivatedEvent."], "discriminator": [20]}, {"name": "SplineDeactivatedEvent", "docs": ["MarketEvent::SplineDeactivated Borsh variant 21.", "Payload type: SplineDeactivatedEvent."], "discriminator": [21]}, {"name": "SplinePriceUpdatedEvent", "docs": ["MarketEvent::SplinePriceUpdated Borsh variant 22.", "Payload type: SplinePriceUpdatedEvent."], "discriminator": [22]}, {"name": "SplineParametersUpdatedEvent", "docs": ["MarketEvent::SplineParametersUpdated Borsh variant 23.", "Payload type: SplineParametersUpdatedEvent."], "discriminator": [23]}, {"name": "MarketAddedEvent", "docs": ["MarketEvent::MarketAdded Borsh variant 24.", "Payload type: MarketAddedEvent."], "discriminator": [24]}, {"name": "MarketStatusChangedEvent", "docs": ["MarketEvent::MarketStatusChanged Borsh variant 25.", "Payload type: MarketStatusChangedEvent."], "discriminator": [25]}, {"name": "MarketParametersUpdatedEvent", "docs": ["MarketEvent::MarketParametersUpdated Borsh variant 26.", "Payload type: MarketParametersUpdatedEvent."], "discriminator": [26]}, {"name": "FundingParametersUpdatedEvent", "docs": ["MarketEvent::FundingParametersUpdated Borsh variant 27.", "Payload type: FundingParametersUpdatedEvent."], "discriminator": [27]}, {"name": "FeesClaimedEvent", "docs": ["MarketEvent::FeesClaimed Borsh variant 28.", "Payload type: FeesClaimedEvent."], "discriminator": [28]}, {"name": "PricesUpdatedEvent", "docs": ["MarketEvent::PricesUpdated Borsh variant 29.", "Payload type: PricesUpdatedEvent."], "discriminator": [29]}, {"name": "LiquidationTransferSummaryEvent", "docs": ["MarketEvent::LiquidationTransferSummary Borsh variant 30.", "Payload type: LiquidationTransferSummaryEvent."], "discriminator": [30]}, {"name": "LiquidationTransferEvent", "docs": ["MarketEvent::LiquidationTransfer Borsh variant 31.", "Payload type: LiquidationTransferEvent."], "discriminator": [31]}, {"name": "LiquidationEvent", "docs": ["MarketEvent::Liquidation Borsh variant 32.", "Payload type: LiquidationEvent."], "discriminator": [32]}, {"name": "CloseMatchedPositionsEvent", "docs": ["MarketEvent::CloseMatchedPositions Borsh variant 33.", "Payload type: CloseMatchedPositionsEvent."], "discriminator": [33]}, {"name": "NameSuccessorEvent", "docs": ["MarketEvent::NameSuccessor Borsh variant 34.", "Payload type: NameSuccessorEvent."], "discriminator": [34]}, {"name": "ClaimAuthorityEvent", "docs": ["MarketEvent::ClaimAuthority Borsh variant 35.", "Payload type: ClaimAuthorityEvent."], "discriminator": [35]}, {"name": "WithdrawStateTransitionEvent", "docs": ["MarketEvent::WithdrawStateTransition Borsh variant 36.", "Payload type: WithdrawStateTransitionEvent."], "discriminator": [36]}, {"name": "PnLEvent", "docs": ["MarketEvent::PnL Borsh variant 37.", "Payload type: PnLEvent."], "discriminator": [37]}, {"name": "StopLossPlacedEvent", "docs": ["MarketEvent::StopLossPlaced Borsh variant 38.", "Payload type: StopLossPlacedEvent."], "discriminator": [38]}, {"name": "StopLossCancelledEvent", "docs": ["MarketEvent::StopLossCancelled Borsh variant 39.", "Payload type: StopLossCancelledEvent."], "discriminator": [39]}, {"name": "StopLossExecutedEvent", "docs": ["MarketEvent::StopLossExecuted Borsh variant 40.", "Payload type: StopLossExecutedEvent."], "discriminator": [40]}, {"name": "ExchangeStatusChangedEvent", "docs": ["MarketEvent::ExchangeStatusChanged Borsh variant 41.", "Payload type: ExchangeStatusChangedEvent."], "discriminator": [41]}, {"name": "TraderCapabilitiesEnabledEvent", "docs": ["MarketEvent::TraderCapabilitiesEnabled Borsh variant 42.", "Payload type: TraderCapabilitiesEnabledEvent."], "discriminator": [42]}, {"name": "TraderFundsWithdrawnFeePaymentEvent", "docs": ["MarketEvent::TraderFundsWithdrawnFeePayment Borsh variant 43.", "Payload type: TraderFundsWithdrawnFeePaymentEvent."], "discriminator": [43]}, {"name": "OrderPacketEvent", "docs": ["MarketEvent::OrderPacket Borsh variant 44.", "Payload type: OrderPacketEvent."], "discriminator": [44]}, {"name": "SetPermissionEvent", "docs": ["MarketEvent::SetPermission Borsh variant 45.", "Payload type: SetPermissionEvent."], "discriminator": [45]}, {"name": "AuthorityChangedEvent", "docs": ["MarketEvent::AuthorityChanged Borsh variant 46.", "Payload type: AuthorityChangedEvent."], "discriminator": [46]}, {"name": "TraderDelegatedEvent", "docs": ["MarketEvent::TraderDelegated Borsh variant 47.", "Payload type: TraderDelegatedEvent."], "discriminator": [47]}, {"name": "AdminParameterUpdatedEvent", "docs": ["MarketEvent::AdminParameterUpdated Borsh variant 48.", "Payload type: AdminParameterUpdatedEvent."], "discriminator": [48]}, {"name": "TraderFeesUpdatedEvent", "docs": ["MarketEvent::TraderFeesUpdated Borsh variant 49.", "Payload type: TraderFeesUpdatedEvent."], "discriminator": [49]}, {"name": "MarketClosedEvent", "docs": ["MarketEvent::MarketClosed Borsh variant 50.", "Payload type: MarketClosedEvent."], "discriminator": [50]}, {"name": "MarketDeletedEvent", "docs": ["MarketEvent::MarketDeleted Borsh variant 51.", "Payload type: MarketDeletedEvent."], "discriminator": [51]}, {"name": "EscrowAccountCreatedEvent", "docs": ["MarketEvent::EscrowAccountCreated Borsh variant 52.", "Payload type: EscrowAccountCreatedEvent."], "discriminator": [52]}, {"name": "EscrowRequestCreatedEvent", "docs": ["MarketEvent::EscrowRequestCreated Borsh variant 53.", "Payload type: EscrowRequestCreatedEvent."], "discriminator": [53]}, {"name": "EscrowRequestAcceptedEvent", "docs": ["MarketEvent::EscrowRequestAccepted Borsh variant 54.", "Payload type: EscrowRequestAcceptedEvent."], "discriminator": [54]}, {"name": "EscrowRequestCancelledEvent", "docs": ["MarketEvent::EscrowRequestCancelled Borsh variant 55.", "Payload type: EscrowRequestCancelledEvent."], "discriminator": [55]}, {"name": "SplinePriceUpdatedWithOrderingEvent", "docs": ["MarketEvent::SplinePriceUpdatedWithOrdering Borsh variant 56.", "Payload type: SplinePriceUpdatedWithOrderingEvent."], "discriminator": [56]}, {"name": "SplineParametersUpdatedWithOrderingEvent", "docs": ["MarketEvent::SplineParametersUpdatedWithOrdering Borsh variant 57.", "Payload type: SplineParametersUpdatedWithOrderingEvent."], "discriminator": [57]}, {"name": "TriggerOrderPlacedEvent", "docs": ["MarketEvent::TriggerOrderPlaced Borsh variant 58.", "Payload type: TriggerOrderPlacedEvent."], "discriminator": [58]}, {"name": "TriggerOrderCancelledEvent", "docs": ["MarketEvent::TriggerOrderCancelled Borsh variant 59.", "Payload type: TriggerOrderCancelledEvent."], "discriminator": [59]}, {"name": "TriggerOrderExecutedEvent", "docs": ["MarketEvent::TriggerOrderExecuted Borsh variant 60.", "Payload type: TriggerOrderExecutedEvent."], "discriminator": [60]}, {"name": "PingInvalidatedEvent", "docs": ["MarketEvent::PingInvalidated Borsh variant 61.", "Payload type: PingInvalidatedEvent."], "discriminator": [61]}, {"name": "PingActivatedEvent", "docs": ["MarketEvent::PingActivated Borsh variant 62.", "Payload type: PingActivatedEvent."], "discriminator": [62]}, {"name": "SplinePositionLimitsConfigUpdatedEvent", "docs": ["MarketEvent::SplinePositionLimitsConfigUpdated Borsh variant 63.", "Payload type: SplinePositionLimitsConfigUpdatedEvent."], "discriminator": [63]}, {"name": "MarketTombstonedEvent", "docs": ["MarketEvent::MarketTombstoned Borsh variant 64.", "Payload type: MarketTombstonedEvent."], "discriminator": [64]}, {"name": "ShutdownClosePositionsEvent", "docs": ["MarketEvent::ShutdownClosePositions Borsh variant 65.", "Payload type: ShutdownClosePositionsEvent."], "discriminator": [65]}, {"name": "OrderResidualDiscardedEvent", "docs": ["MarketEvent::OrderResidualDiscarded Borsh variant 66.", "Payload type: OrderResidualDiscardedEvent."], "discriminator": [66]}], "errors": []} diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml index 7189c4651..aca6b8096 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/overrides.yaml @@ -29,14 +29,16 @@ templates: - id: phoenix-direct-mark-risk-shock name: Phoenix Direct Mark Risk Shock description: Set exact mark-price ticks for one market in a validated Phoenix Eternal PerpAssetMap. - idl_account_name: DirectMarkOverride + idl_account_name: PerpAssetMap properties: - path: symbol + value_type: string type: dynamic_ref label: Market Symbol source: list_phoenix_markets description: Exact Phoenix market symbol - path: target_ticks + value_type: string label: Target Mark Ticks description: Exact unsigned mark-price ticks encoded as a decimal string llm_context: | @@ -53,17 +55,20 @@ templates: - id: phoenix-reference-price-divergence name: Phoenix Spot/Perp Reference Divergence description: Set independent spot and external-perp reference ticks while preserving the current mark price. - idl_account_name: ReferencePriceOverride + idl_account_name: PerpAssetMap properties: - path: symbol + value_type: string type: dynamic_ref label: Market Symbol source: list_phoenix_markets description: Exact Phoenix market symbol - path: spot_ticks + value_type: string label: Spot Reference Ticks description: Exact spot reference ticks encoded as a decimal string - path: perp_ticks + value_type: string label: Perp Reference Ticks description: Exact external-perp reference ticks encoded as a decimal string llm_context: | diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs index 9c871b260..8bfacafab 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs @@ -419,7 +419,7 @@ fn parse_unsigned_ticks(value: &serde_json::Value, field: &str) -> SurfpoolResul fn parse_quote_lot_collateral(value: &str) -> SurfpoolResult { value.parse::().map_err(|_| { SurfpoolError::internal( - "quote_lot_collateral must be a signed 64-bit integer encoded as a string", + "targetQuoteLots must be a signed 64-bit integer encoded as a string", ) }) } @@ -467,6 +467,15 @@ pub(crate) mod tests { } } + fn trader_account_for(trader: Pubkey, collateral: i64) -> Account { + let mut account = Account { + data: trader_fixture(collateral, 1, 2), + ..trader_account() + }; + account.data[24..56].copy_from_slice(trader.as_ref()); + account + } + pub(crate) fn perp_asset_map_fixture() -> Vec { let prefix = BASE64_STANDARD .decode(SOL_PERP_ASSET_MAP_PREFIX_B64) @@ -506,9 +515,13 @@ pub(crate) mod tests { #[test] fn builds_one_editable_collateral_override_for_the_requested_trader() { let trader = Pubkey::new_unique(); - let preparation = - build_phoenix_collateral_scenario(trader, &trader_account(), "-9007199254740993", None) - .unwrap(); + let preparation = build_phoenix_collateral_scenario( + trader, + &trader_account_for(trader, 0), + "-9007199254740993", + None, + ) + .unwrap(); assert_eq!(preparation.overrides.len(), 1); @@ -532,10 +545,7 @@ pub(crate) mod tests { #[test] fn builder_refuses_to_raise_collateral_above_its_vault_backing() { let trader = Pubkey::new_unique(); - let funded = Account { - data: trader_fixture(500, 1, 2), - ..trader_account() - }; + let funded = trader_account_for(trader, 500); let raised = build_phoenix_collateral_scenario(trader, &funded, "501", None).unwrap_err(); assert!(raised.to_string().contains("can only lower collateral")); diff --git a/crates/core/src/scenarios/registry.rs b/crates/core/src/scenarios/registry.rs index ddcc90f9b..0813be859 100644 --- a/crates/core/src/scenarios/registry.rs +++ b/crates/core/src/scenarios/registry.rs @@ -444,6 +444,14 @@ mod tests { "phoenix-reference-price-divergence", ] { let template = registry.get(id).expect("phoenix market template exists"); + assert_eq!(template.account_type, "PerpAssetMap"); + for property in &template.properties { + assert_eq!( + serde_json::to_value(property).unwrap()["value_type"], + "string" + ); + } + let symbol = template .properties .iter() @@ -465,6 +473,23 @@ mod tests { } } + #[test] + fn phoenix_idl_contains_only_real_accounts_with_unique_discriminators() { + let idl: surfpool_types::Idl = serde_json::from_str(PHOENIX_ETERNAL_IDL_CONTENT).unwrap(); + let mut discriminators = std::collections::HashSet::new(); + for account in &idl.accounts { + assert!( + discriminators.insert(&account.discriminator), + "duplicate {}", + account.name + ); + } + for name in ["DirectMarkOverride", "ReferencePriceOverride"] { + assert!(!idl.accounts.iter().any(|account| account.name == name)); + assert!(!idl.types.iter().any(|definition| definition.name == name)); + } + } + #[test] fn test_registry_loads_all_protocols() { let registry = TemplateRegistry::new(); diff --git a/crates/core/src/surfnet/svm.rs b/crates/core/src/surfnet/svm.rs index b1763e015..4b0a9870b 100644 --- a/crates/core/src/surfnet/svm.rs +++ b/crates/core/src/surfnet/svm.rs @@ -94,7 +94,7 @@ use crate::{ scenarios::{ TemplateRegistry, protocols::phoenix_eternal::v1::{ - collateral::collateral_override_value, + collateral::{collateral_override_value, validate_collateral_fields}, state_builder::{ PHOENIX_ETERNAL_PROGRAM_ID, forge_phoenix_override, is_phoenix_perp_asset_map_account, is_phoenix_trader_account, @@ -2944,15 +2944,9 @@ impl SurfnetSvm { let is_phoenix_trader = account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID && is_phoenix_trader_account(account.data()); if is_phoenix_trader { - if let Some(legacy) = account_values.remove("quote_lot_collateral") { - if account_values.contains_key("traderState.quoteLotCollateral") { - warn!( - "Skipping override {}: Phoenix collateral must use only traderState.quoteLotCollateral", - override_instance.id - ); - continue; - } - account_values.insert("traderState.quoteLotCollateral".to_string(), legacy); + if let Err(error) = validate_collateral_fields(&account_values) { + warn!("Skipping override {}: {}", override_instance.id, error); + continue; } if let Some(value) = account_values.get_mut("traderState.quoteLotCollateral") { match collateral_override_value(value) { diff --git a/crates/core/src/tests/phoenix/mod.rs b/crates/core/src/tests/phoenix/mod.rs index 6c4691f18..9590edbf6 100644 --- a/crates/core/src/tests/phoenix/mod.rs +++ b/crates/core/src/tests/phoenix/mod.rs @@ -17,6 +17,9 @@ //! Nothing here hunts for a market that happens to sit in an interesting state: the live //! accounts are the raw material, and the production builders prepare the scene. +use std::collections::HashMap; + +use bytemuck::{Pod, Zeroable}; use phoenix_rise_accounts::{ PhoenixAccount, global_config::GlobalConfig, @@ -24,9 +27,6 @@ use phoenix_rise_accounts::{ perp_asset_map::PerpAssetMap, trader::{Trader, TraderHeader}, }; -use std::collections::HashMap; - -use bytemuck::{Pod, Zeroable}; use solana_account::Account; use solana_account_decoder::UiAccountEncoding; use solana_clock::Clock; @@ -41,7 +41,6 @@ use solana_rpc_client_api::{ }; use solana_signer::Signer; use solana_transaction::Transaction; - use surfpool_types::AccountAddress; use crate::{ @@ -918,7 +917,8 @@ fn phoenix_reference_price_scenario( #[tokio::test(flavor = "multi_thread")] async fn materialize_patches_only_phoenix_trader_collateral() { let trader = Pubkey::new_unique(); - let base = phoenix_trader_fixture(6_996_825_500); + let mut base = phoenix_trader_fixture(6_996_825_500); + base[24..56].copy_from_slice(trader.as_ref()); let scenario = phoenix_collateral_scenario(trader, serde_json::json!("371499999"), false); let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); let locker = SurfnetSvmLocker::new(svm); diff --git a/crates/mcp/src/surfpool/mod.rs b/crates/mcp/src/surfpool/mod.rs index baae0c1f7..491a21983 100644 --- a/crates/mcp/src/surfpool/mod.rs +++ b/crates/mcp/src/surfpool/mod.rs @@ -19,13 +19,18 @@ use start_surfnet::StartSurfnetResponse; use surfpool_core::{ scenarios::{ TemplateRegistry, - protocols::phoenix_eternal::v1::state_builder::{ - PHOENIX_GLOBAL_CONFIG, build_phoenix_collateral_scenario, - phoenix_global_trader_index_address, phoenix_market_symbols, - phoenix_perp_asset_map_address, - }, - protocols::pump::v1::graduation_builder::{ - build_pump_graduation_scenario, pump_graduation_addresses, + protocols::{ + phoenix_eternal::v1::{ + collateral::{trader_header, validate_collateral_fields}, + state_builder::{ + PHOENIX_GLOBAL_CONFIG, build_phoenix_collateral_scenario, + phoenix_global_trader_index_address, phoenix_market_symbols, + phoenix_perp_asset_map_address, + }, + }, + pump::v1::graduation_builder::{ + build_pump_graduation_scenario, pump_graduation_addresses, + }, }, }, solana_account::Account, @@ -449,6 +454,45 @@ impl Surfpool { .collect()) } + async fn build_phoenix_collateral_scenario_from_surfnet( + &self, + params: &CreatePhoenixCollateralScenarioParams, + ) -> Result { + let trader = Pubkey::from_str(params.trader.trim()) + .map_err(|error| format!("Invalid Trader pubkey: {error}"))?; + let accounts = self + .fetch_surfnet_accounts(params.surfnet_port, &[trader]) + .await?; + let trader_account = accounts[0] + .as_ref() + .ok_or_else(|| format!("Phoenix Trader account {trader} was not found"))?; + let header = trader_header(&trader, trader_account).map_err(|error| error.to_string())?; + let index = if header.trader_state.is_hot() { + let globals = self + .fetch_surfnet_accounts(params.surfnet_port, &[PHOENIX_GLOBAL_CONFIG]) + .await?; + let global = globals[0] + .as_ref() + .ok_or("Phoenix GlobalConfig was not found")?; + let index_address = + phoenix_global_trader_index_address(global).map_err(|error| error.to_string())?; + self.fetch_surfnet_accounts(params.surfnet_port, &[index_address]) + .await? + .into_iter() + .next() + .flatten() + } else { + None + }; + build_phoenix_collateral_scenario( + trader, + trader_account, + ¶ms.target_quote_lots, + index.as_ref(), + ) + .map_err(|error| error.to_string()) + } + async fn stage_scenario(&self, scenario: Scenario) -> Result { let endpoint = format!( "http://127.0.0.1:{}/v1/scenarios", @@ -844,6 +888,12 @@ impl Surfpool { // Get the template for validation and normalization if let Some(template) = registry.get(&override_instance.template_id) { + if template.id == "phoenix-trader-collateral-stress" { + if let Err(error) = validate_collateral_fields(&override_instance.values) { + validation_errors.push(error.to_string()); + continue; + } + } // Normalize: Extract values from malformed PDA seeds // LLMs sometimes put values directly in seeds instead of in values map if let surfpool_types::AccountAddress::Pda { seeds, .. } = @@ -1077,50 +1127,12 @@ impl Surfpool { &self, Parameters(params): Parameters, ) -> Result { - let trader = match Pubkey::from_str(params.trader.trim()) { - Ok(trader) => trader, - Err(error) => { - return Ok(scenario_tool_error(format!( - "Invalid Trader pubkey: {error}" - ))); - } - }; - let accounts = match self - .fetch_surfnet_accounts(params.surfnet_port, &[trader, PHOENIX_GLOBAL_CONFIG]) + let scenario = match self + .build_phoenix_collateral_scenario_from_surfnet(¶ms) .await { - Ok(accounts) => accounts, - Err(error) => return Ok(scenario_tool_error(error)), - }; - let Some(trader_account) = accounts[0].as_ref() else { - return Ok(scenario_tool_error(format!( - "Phoenix Trader account {trader} was not found" - ))); - }; - let Some(global_account) = accounts[1].as_ref() else { - return Ok(scenario_tool_error( - "Phoenix GlobalConfig was not found".to_string(), - )); - }; - let index_address = match phoenix_global_trader_index_address(global_account) { - Ok(address) => address, - Err(error) => return Ok(scenario_tool_error(error.to_string())), - }; - let index_accounts = match self - .fetch_surfnet_accounts(params.surfnet_port, &[index_address]) - .await - { - Ok(accounts) => accounts, - Err(error) => return Ok(scenario_tool_error(error)), - }; - let scenario = match build_phoenix_collateral_scenario( - trader, - trader_account, - ¶ms.target_quote_lots, - index_accounts[0].as_ref(), - ) { Ok(scenario) => scenario, - Err(error) => return Ok(scenario_tool_error(error.to_string())), + Err(error) => return Ok(scenario_tool_error(error)), }; self.stage_scenario(scenario).await @@ -1544,6 +1556,121 @@ mod tests { ); } + #[test] + fn phoenix_collateral_fetches_dependencies_only_for_hot_traders() { + use surfpool_core::{ + jsonrpc_core::{IoHandler, Params}, + jsonrpc_http_server::ServerBuilder, + scenarios::protocols::phoenix_eternal::v1::state_builder::PHOENIX_ETERNAL_PROGRAM_ID, + }; + + for (hot, matching_key) in [(false, true), (true, true), (false, false)] { + let trader = Pubkey::new_unique(); + let mut data = vec![0_u8; 240]; + data[..8].copy_from_slice(&[41, 97, 73, 105, 110, 214, 112, 9]); + data[24..56].copy_from_slice(if matching_key { + trader.as_ref() + } else { + PHOENIX_GLOBAL_CONFIG.as_ref() + }); + data[88..96].copy_from_slice(&50_i64.to_le_bytes()); + data[96..100].copy_from_slice(&u32::from(hot).to_le_bytes()); + let account = serde_json::json!({ + "lamports": 1, "owner": PHOENIX_ETERNAL_PROGRAM_ID.to_string(), + "data": [bs58::encode(data).into_string(), "base58"], + "executable": false, "rentEpoch": 0, + }); + let requests = Arc::new(std::sync::Mutex::new(Vec::new())); + let observed = requests.clone(); + let mut io = IoHandler::new(); + io.add_sync_method("getMultipleAccounts", move |params: Params| { + let params: Vec = params.parse()?; + let keys: Vec = serde_json::from_value(params[0].clone()).unwrap(); + observed.lock().unwrap().push(keys.clone()); + let values: Vec = keys + .iter() + .map(|key| { + if key == &trader.to_string() { + account.clone() + } else { + Value::Null + } + }) + .collect(); + Ok(serde_json::json!({"context": {"slot": 100}, "value": values})) + }); + let server = ServerBuilder::new(io) + .start_http(&"127.0.0.1:0".parse().unwrap()) + .unwrap(); + let params = CreatePhoenixCollateralScenarioParams { + surfnet_port: Some(server.address().port()), + trader: trader.to_string(), + target_quote_lots: "-9007199254740993".to_string(), + }; + let runtime = tokio::runtime::Runtime::new().unwrap(); + let result = runtime + .block_on(Surfpool::new().build_phoenix_collateral_scenario_from_surfnet(¶ms)); + server.close(); + let mut expected = vec![vec![trader.to_string()]]; + if !matching_key { + assert!( + result + .unwrap_err() + .contains("key does not match its account address") + ); + } else if hot { + assert!(result.unwrap_err().contains("GlobalConfig was not found")); + expected.push(vec![PHOENIX_GLOBAL_CONFIG.to_string()]); + } else { + let scenario = result.unwrap(); + assert_eq!(scenario.overrides.len(), 1); + assert_eq!( + scenario.overrides[0].account, + surfpool_types::AccountAddress::Pubkey(trader.to_string()) + ); + assert_eq!( + scenario.overrides[0].values["traderState.quoteLotCollateral"], + "-9007199254740993" + ); + assert!(!scenario.overrides[0].fetch_before_use); + } + assert_eq!(*requests.lock().unwrap(), expected); + } + } + + #[tokio::test] + async fn create_scenario_rejects_legacy_phoenix_collateral_before_staging() { + for include_canonical in [false, true] { + let mut scenario = Scenario::new("old collateral".to_string(), "old input".to_string()); + let mut values = + HashMap::from([("quote_lot_collateral".to_string(), serde_json::json!("1"))]); + if include_canonical { + values.insert( + "traderState.quoteLotCollateral".to_string(), + serde_json::json!("2"), + ); + } + scenario.add_override( + surfpool_types::OverrideInstance::new( + "phoenix-trader-collateral-stress".to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(Pubkey::new_unique().to_string()), + ) + .with_values(values), + ); + let result = Surfpool::new() + .create_scenario(Parameters(scenario)) + .await + .unwrap(); + assert!(json_of(&result)["url"].is_null()); + assert!( + json_of(&result) + .to_string() + .contains("use traderState.quoteLotCollateral") + ); + } + } + fn json_of(result: &CallToolResult) -> serde_json::Value { let text = &result.content[0].as_text().expect("text content").text; serde_json::from_str(text).expect("valid JSON payload") diff --git a/crates/types/src/scenarios.rs b/crates/types/src/scenarios.rs index 6b4c9d2ab..fcf51a68f 100644 --- a/crates/types/src/scenarios.rs +++ b/crates/types/src/scenarios.rs @@ -283,6 +283,8 @@ pub struct Property { /// For dynamic_ref type: the MCP tool whose result supplies the dropdown options #[serde(default, skip_serializing_if = "Option::is_none")] pub source: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value_type: Option, } impl Property { @@ -295,6 +297,7 @@ impl Property { description: None, constant: None, source: None, + value_type: None, } } @@ -307,6 +310,7 @@ impl Property { description: None, constant: Some(constant.into()), source: None, + value_type: None, } } @@ -858,6 +862,8 @@ pub enum YamlProperty { /// For dynamic_ref type: the MCP tool whose result supplies the options #[serde(default)] source: Option, + #[serde(default)] + value_type: Option, }, } @@ -872,6 +878,7 @@ impl From for Property { description, constant, source, + value_type, } => { let kind = match kind.as_deref() { Some("constant_ref") => PropertyKind::ConstantRef, @@ -885,6 +892,7 @@ impl From for Property { description, constant, source, + value_type, } } } @@ -1182,3 +1190,34 @@ mod tests { } } } + +#[cfg(test)] +mod property_value_type_tests { + use super::*; + + #[test] + fn explicit_value_types_roundtrip_without_changing_legacy_properties() { + for input in [ + serde_json::json!("amount"), + serde_json::json!({"path": "amount"}), + ] { + let yaml: YamlProperty = serde_json::from_value(input).unwrap(); + let property = Property::from(yaml); + assert!(property.value_type.is_none()); + assert!( + serde_json::to_value(property) + .unwrap() + .get("value_type") + .is_none() + ); + } + let yaml: YamlProperty = serde_json::from_value(serde_json::json!({ + "path": "target_ticks", "value_type": "string" + })) + .unwrap(); + let property = Property::from(yaml); + let json = serde_json::to_value(&property).unwrap(); + assert_eq!(json["value_type"], "string"); + assert_eq!(serde_json::from_value::(json).unwrap(), property); + } +} From fd2f7dd14055bfd64b14057a2724b1d8f0089c6b Mon Sep 17 00:00:00 2001 From: 92Infinitus92 <92georgepetroff92@gmail.com> Date: Tue, 15 Sep 2026 09:43:05 +0300 Subject: [PATCH 5/6] fix(phoenix): guard hot Trader fields and check every Trader key at Play Only traderState.quoteLotCollateral is mirrored into the GlobalTraderIndex, so any other TraderState write on a hot Trader is now refused at scenario creation and skipped at Play. Every Trader override checks the embedded header key at Play, not just the hot ones. The index and GlobalConfig stay local-first: a refresh of one Trader must not reinstall the shared index over mirrors that earlier scenarios prepared. --- .../protocols/phoenix-eternal/README.md | 6 ++ .../phoenix-eternal/v1/collateral.rs | 60 +++++++++++++++++++ crates/core/src/surfnet/svm.rs | 18 +++++- crates/mcp/src/surfpool/mod.rs | 30 +++++++++- 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md index fa34b07c6..6fbfc3de9 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md @@ -18,6 +18,12 @@ values as decimal strings so values outside JavaScript's safe integer range rema Collateral overrides use `traderState.quoteLotCollateral`. The former `quote_lot_collateral` name is rejected by MCP scenario creation; an existing scenario using it is skipped at Play with an explicit warning naming the replacement. Update that scenario's values before playing it. +On a hot Trader only `traderState.quoteLotCollateral` can be overridden: the GlobalTraderIndex +mirror carries just that field, so other `traderState` writes are rejected at creation and skipped +at Play. A Trader whose header key differs from its address is skipped at Play as well. The +GlobalConfig and GlobalTraderIndex a hot override needs are fetched once and then kept as local +state: a later `fetchBeforeUse` on another Trader does not reinstall them, so mirrors prepared by +earlier scenarios survive. Market templates identify the real `PerpAssetMap` account. Their `symbol` and tick properties carry `value_type: string` so Studio can render scenario inputs without synthetic IDL accounts. diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs index 4bbafc018..0e16a2216 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs @@ -47,6 +47,23 @@ pub fn validate_collateral_fields( Ok(()) } +/// A hot Trader's TraderState is mirrored in the GlobalTraderIndex and the mirror update carries +/// only the collateral, so any other TraderState write would leave the two copies disagreeing. +pub fn validate_hot_trader_fields( + values: &HashMap, +) -> SurfpoolResult<()> { + if let Some(field) = values.keys().find(|field| { + field.as_str() == "traderState" + || (field.starts_with("traderState.") + && field.as_str() != "traderState.quoteLotCollateral") + }) { + return Err(SurfpoolError::internal(format!( + "Phoenix TraderState field '{field}' is unsupported; only traderState.quoteLotCollateral is mirrored into the GlobalTraderIndex for hot Traders" + ))); + } + Ok(()) +} + pub fn index_trader_state_range( index: &Account, trader_key: &[u8; 32], @@ -435,6 +452,7 @@ mod tests { "missing account", "conflicting fields", "mismatched key", + "unsupported field", ] { let trader = Pubkey::new_from_array(FIRST_KEY); let index_key = Pubkey::new_unique(); @@ -447,6 +465,11 @@ mod tests { .unwrap(); match failure { "mismatched key" => before_trader.data[24..56].copy_from_slice(&SECOND_KEY), + "unsupported field" => { + scenario.overrides[0] + .values + .insert("traderState.flags".to_string(), serde_json::json!(0)); + } "conflicting fields" => { scenario.overrides[0] .values @@ -484,6 +507,43 @@ mod tests { } } + #[test] + fn hot_trader_fields_allow_only_the_mirrored_collateral() { + let collateral_only = HashMap::from([( + "traderState.quoteLotCollateral".to_string(), + serde_json::json!("1"), + )]); + assert!(validate_hot_trader_fields(&collateral_only).is_ok()); + for field in ["traderState.flags", "traderState"] { + let values = HashMap::from([(field.to_string(), serde_json::json!(0))]); + let error = validate_hot_trader_fields(&values).unwrap_err().to_string(); + assert!( + error.contains(field) && error.contains("hot Traders"), + "{error}" + ); + } + } + + #[tokio::test] + async fn materialization_skips_a_cold_trader_whose_header_key_mismatches() { + use crate::surfnet::svm::SurfnetSvm; + + let trader = Pubkey::new_from_array(FIRST_KEY); + let matching = trader_account(FIRST_KEY, 9_999, false); + let scenario = build_phoenix_collateral_scenario(trader, &matching, "1", None).unwrap(); + let mut on_chain = trader_account(SECOND_KEY, 9_999, false); + on_chain.lamports = 1; + let (mut svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + svm.set_account(&trader, on_chain.clone()).unwrap(); + svm.register_scenario(scenario, Some(100)).unwrap(); + + svm.materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); + + assert_eq!(svm.get_account(&trader).unwrap().unwrap(), on_chain); + } + #[test] fn trader_idl_keeps_max_positions_and_preference_bits_separate() { use crate::{scenarios::registry::PHOENIX_ETERNAL_IDL_CONTENT, surfnet::svm::SurfnetSvm}; diff --git a/crates/core/src/surfnet/svm.rs b/crates/core/src/surfnet/svm.rs index 4b0a9870b..27b134e10 100644 --- a/crates/core/src/surfnet/svm.rs +++ b/crates/core/src/surfnet/svm.rs @@ -94,7 +94,10 @@ use crate::{ scenarios::{ TemplateRegistry, protocols::phoenix_eternal::v1::{ - collateral::{collateral_override_value, validate_collateral_fields}, + collateral::{ + collateral_override_value, trader_header, validate_collateral_fields, + validate_hot_trader_fields, + }, state_builder::{ PHOENIX_ETERNAL_PROGRAM_ID, forge_phoenix_override, is_phoenix_perp_asset_map_account, is_phoenix_trader_account, @@ -2944,10 +2947,23 @@ impl SurfnetSvm { let is_phoenix_trader = account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID && is_phoenix_trader_account(account.data()); if is_phoenix_trader { + let header = match trader_header(&account_pubkey, &account) { + Ok(header) => header, + Err(error) => { + warn!("Skipping override {}: {}", override_instance.id, error); + continue; + } + }; if let Err(error) = validate_collateral_fields(&account_values) { warn!("Skipping override {}: {}", override_instance.id, error); continue; } + if header.trader_state.is_hot() { + if let Err(error) = validate_hot_trader_fields(&account_values) { + warn!("Skipping override {}: {}", override_instance.id, error); + continue; + } + } if let Some(value) = account_values.get_mut("traderState.quoteLotCollateral") { match collateral_override_value(value) { Ok(parsed) => *value = parsed, diff --git a/crates/mcp/src/surfpool/mod.rs b/crates/mcp/src/surfpool/mod.rs index 491a21983..369f0909c 100644 --- a/crates/mcp/src/surfpool/mod.rs +++ b/crates/mcp/src/surfpool/mod.rs @@ -21,7 +21,9 @@ use surfpool_core::{ TemplateRegistry, protocols::{ phoenix_eternal::v1::{ - collateral::{trader_header, validate_collateral_fields}, + collateral::{ + trader_header, validate_collateral_fields, validate_hot_trader_fields, + }, state_builder::{ PHOENIX_GLOBAL_CONFIG, build_phoenix_collateral_scenario, phoenix_global_trader_index_address, phoenix_market_symbols, @@ -889,7 +891,9 @@ impl Surfpool { // Get the template for validation and normalization if let Some(template) = registry.get(&override_instance.template_id) { if template.id == "phoenix-trader-collateral-stress" { - if let Err(error) = validate_collateral_fields(&override_instance.values) { + if let Err(error) = validate_collateral_fields(&override_instance.values) + .and_then(|_| validate_hot_trader_fields(&override_instance.values)) + { validation_errors.push(error.to_string()); continue; } @@ -1638,6 +1642,28 @@ mod tests { } } + #[tokio::test] + async fn create_scenario_rejects_other_trader_state_fields_before_staging() { + let mut scenario = Scenario::new("flags".to_string(), "not mirrored".to_string()); + scenario.add_override( + surfpool_types::OverrideInstance::new( + "phoenix-trader-collateral-stress".to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(Pubkey::new_unique().to_string()), + ) + .with_values(HashMap::from([( + "traderState.flags".to_string(), + serde_json::json!(0), + )])), + ); + let result = Surfpool::new() + .create_scenario(Parameters(scenario)) + .await + .unwrap(); + assert!(json_of(&result)["url"].is_null()); + assert!(json_of(&result).to_string().contains("traderState.flags")); + } + #[tokio::test] async fn create_scenario_rejects_legacy_phoenix_collateral_before_staging() { for include_canonical in [false, true] { From 1d096ff6cc1c1e9767269b08c0500e4cf47fa04b Mon Sep 17 00:00:00 2001 From: 92Infinitus92 <92georgepetroff92@gmail.com> Date: Tue, 15 Sep 2026 13:21:53 +0300 Subject: [PATCH 6/6] refactor(phoenix): trim duplicate tests and simplify the writers No behavior change. The PR shrinks by about 600 lines: - live tests: drop the three synthetic materialize_* copies of the unit tests and the duplicate second-market block; one phoenix_market_scenario helper replaces the three scenario builders - collateral: one table-driven index lookup test, cold-trader Play test covers the happy path and the mismatched key, duplicate builder and header tests removed, one parse_quote_lot_collateral for both callers - state_builder: forge dispatch matches on the sorted key set, the owner check runs once, unique_subslice_offset is a windows scan, the patch and forge test pairs are merged - mcp: list_phoenix_markets uses a fallible inner function; the two staging rejection tests become one table - README: one verification section instead of three overlapping ones --- .../protocols/phoenix-eternal/README.md | 86 +-- .../phoenix-eternal/v1/collateral.rs | 207 ++----- .../phoenix-eternal/v1/state_builder.rs | 560 +++++++----------- crates/core/src/scenarios/registry.rs | 17 - crates/core/src/surfnet/svm.rs | 36 +- crates/core/src/tests/phoenix/mod.rs | 313 ++-------- crates/mcp/src/surfpool/mod.rs | 155 ++--- 7 files changed, 388 insertions(+), 986 deletions(-) diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md index 6fbfc3de9..70f5ed2c8 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/README.md +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/README.md @@ -65,10 +65,6 @@ The collateral builder and live market catalog are available through MCP: The collateral tool returns a Studio editor URL. The backend reads the live Trader account and refuses a target above the trader’s effective collateral. -The market scenarios need no tool: their templates carry the perp asset map address, so a client -fills in the values and posts the scenario to `/v1/scenarios`, which is what the Studio preset -does. The liquidation cascade combines those two templates in one scenario at slots 0 and 1. - ## What each preparation guarantees - Collateral stress uses the IDL for `Trader.traderState.quoteLotCollateral`. For a hot trader, @@ -120,73 +116,35 @@ and Clock together with `getMultipleAccounts` after discovering the addresses; t oracle timestamps or fabricate trader positions. An old fork can also fail price-staleness guards independently of whether the override wrote the correct values. -## Layout drift +## Verification against mainnet Phoenix is zero-copy, so decoding an account built by these tests can never disagree with the -decoder that built it. Drift is caught against live mainnet accounts instead: - -```sh -cargo test -p surfpool-core --features integration-tests tests::phoenix -``` - -Those tests resolve the live account graph, assert the typed invariants the program relies on, -and prove an override on a live account changes only its target bytes. Set -`SURFPOOL_TEST_RPC_URL` to use a private endpoint if the public one rate-limits. - -## Behavioral verification - -To check reference prices independently on the live SOL and BTC markets: - -```sh -cargo test -p surfpool-core --features integration-tests reference_prices_change_hawkeye_index_on_live_markets -``` - -The behavioral test runs with the rest of the integration suite; there is nothing to install by -hand and no snapshots to keep current, since the deployed bytecode is fetched straight from the -chain: +decoder that built it. Layout drift and behavior are both checked against live mainnet accounts: ```sh cargo test -p surfpool-core --features integration-tests tests::phoenix ``` -It forks the live account graph, discovers a live hot Trader that carries collateral and a long -position, and loads the deployed Phoenix Eternal and Hawkeye bytecode straight from their -ProgramData accounts (`B5ayDaz9HegiNZqYeBtcFqfZBVSGwjB2CJgHshoSfMQg` and -`Gv1WgG864CQqF5vedJVbpnhpRpRbTW1A7SyARzSw9B4Y`), cached under the system temp directory as -`surfpool-phoenix-eternal.so` and `surfpool-phoenix-hawkeye.so`. Delete those files to pick up a -program upgrade; the cached bytecode is otherwise reused as-is. - -The collateral check uses the production builder, verifies creation leaves the Trader and -GlobalTraderIndex unchanged, and compares both complete accounts after Play: only their -collateral fields may change. A real Hawkeye transaction must read collateral `1` and report -the trader liquidatable. This runs in the isolated test VM, outside Studio's Transaction Inspector. - -The raw material is selected, not arbitrary: it walks the program's Trader accounts for one that -carries collateral, and for the mark-shock and cascade tests, one that also holds a long position, -since a downward shock only threatens a long. The risk condition itself — a trader actually -falling below its maintenance requirement — is produced by the preparations, not found -pre-existing. The run fails loudly with `no eligible live candidate` rather than skipping if the -raw material is missing. It proves, through real Phoenix and Hawkeye execution: - -- collateral stress lands in the account the program reads, and lowers the collateral its risk - engine can count on; +Set `SURFPOOL_TEST_RPC_URL` to use a private endpoint if the public one rate-limits. The suite +resolves the live account graph, asserts the typed invariants the program relies on, proves an +override on a live account changes only its target bytes, and runs the deployed Phoenix Eternal +and Hawkeye bytecode in the test VM. The bytecode is read from the ProgramData accounts +(`B5ayDaz9HegiNZqYeBtcFqfZBVSGwjB2CJgHshoSfMQg` and `Gv1WgG864CQqF5vedJVbpnhpRpRbTW1A7SyARzSw9B4Y`) +and cached under the system temp directory as `surfpool-phoenix-eternal.so` and +`surfpool-phoenix-hawkeye.so`; delete those files to pick up a program upgrade. + +The behavioral run discovers a live hot Trader that carries collateral and a long position, and +fails loudly with `no eligible live candidate` rather than skipping if none exists. The risk +condition is produced by the preparations, not found pre-existing. Through real Phoenix and +Hawkeye execution it proves that: + +- collateral stress lands in the account the program reads, lowers the collateral its risk + engine can count on, and a Hawkeye margin view reports the trader liquidatable; - the two cascade stages arrive in order: the prepared collateral at the first slot, and the mark the program reads shocked at the next; -- spot/perp divergence moves the cached reference away from the mark while the mark itself - stands; -- a second live market takes the same direct-mark preparation, so the templates are not - market-specific. - -Expected test summary: - -```text -test result: ok. 9 passed; 0 failed; 0 ignored -``` - -## Why there is no orderbook-consumption test +- spot/perp divergence moves the cached index away from the mark on both live markets while + the mark itself stands. -An earlier version proved a bot could consume the prepared orderbook by signing a market sell as -the localnet fixture's taker. A live Trader's key is not ours to sign with, so that test went -with the fixture. What it covered is still covered: the market and spline accounts are never -written by any Phoenix preparation, which the byte-level assertions on live accounts enforce -directly, and Hawkeye reads the resulting book state in the behavioral test. +The market and spline accounts are never written by any Phoenix preparation; the byte-level +assertions on live accounts enforce that directly, and Hawkeye reads the resulting book state +in the behavioral run. diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs index 0e16a2216..8e8500e1f 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/collateral.rs @@ -140,11 +140,10 @@ pub fn effective_collateral(header: &TraderHeader, index: Option<&Account>) -> S )) } -pub fn collateral_override_value(value: &serde_json::Value) -> SurfpoolResult { +pub fn parse_quote_lot_collateral(value: &serde_json::Value) -> SurfpoolResult { value .as_i64() - .or_else(|| value.as_str().and_then(|value| value.parse::().ok())) - .map(serde_json::Value::from) + .or_else(|| value.as_str().and_then(|value| value.parse().ok())) .ok_or_else(|| { SurfpoolError::internal("Phoenix collateral must be a signed 64-bit integer") }) @@ -164,12 +163,12 @@ mod tests { fn collateral_values_preserve_signed_integer_precision() { for value in [i64::MIN, -9_007_199_254_740_993, 0, i64::MAX] { assert_eq!( - collateral_override_value(&serde_json::json!(value.to_string())).unwrap(), - serde_json::json!(value) + parse_quote_lot_collateral(&serde_json::json!(value.to_string())).unwrap(), + value ); assert_eq!( - collateral_override_value(&serde_json::json!(value)).unwrap(), - serde_json::json!(value) + parse_quote_lot_collateral(&serde_json::json!(value)).unwrap(), + value ); } for value in [ @@ -177,7 +176,7 @@ mod tests { serde_json::json!(-1.5), serde_json::json!(null), ] { - assert!(collateral_override_value(&value).is_err()); + assert!(parse_quote_lot_collateral(&value).is_err()); } } @@ -230,7 +229,7 @@ mod tests { } #[test] - fn index_lookup_selects_reachable_keys_and_ignores_freed_duplicate() { + fn index_lookup_selects_reachable_keys_and_rejects_malformed_trees() { let index = index_account(); assert_eq!( index_trader_state_range(&index, &FIRST_KEY).unwrap(), @@ -240,65 +239,49 @@ mod tests { index_trader_state_range(&index, &SECOND_KEY).unwrap(), 208..224 ); - - let mut only_freed_match = index.clone(); - only_freed_match.data[112..144].copy_from_slice(&[33; 32]); - assert!(index_trader_state_range(&only_freed_match, &FIRST_KEY).is_err()); - } - - #[test] - fn index_lookup_rejects_cycles_wrong_size_and_missing_key() { - let mut cycle = index_account(); - write_u32(&mut cycle.data, 96, 2); - assert!(index_trader_state_range(&cycle, &FIRST_KEY).is_err()); - - let mut wrong_size = index_account(); - write_u32(&mut wrong_size.data, 48, 3); - assert!(index_trader_state_range(&wrong_size, &FIRST_KEY).is_err()); - assert!(index_trader_state_range(&index_account(), &[44; 32]).is_err()); - } - - #[test] - fn index_lookup_rejects_invalid_node_addresses_and_duplicate_reachable_keys() { - let mut out_of_bounds = index_account(); - write_u32(&mut out_of_bounds.data, 60, 100); - write_u32(&mut out_of_bounds.data, 164, 4); - assert!(index_trader_state_range(&out_of_bounds, &FIRST_KEY).is_err()); - - let mut unallocated = index_account(); - write_u32(&mut unallocated.data, 60, 2); - assert!(index_trader_state_range(&unallocated, &FIRST_KEY).is_err()); - - let mut duplicate = index_account(); - duplicate.data[176..208].copy_from_slice(&FIRST_KEY); - assert!(index_trader_state_range(&duplicate, &FIRST_KEY).is_err()); - } - - #[test] - fn index_lookup_rejects_wrong_owner_discriminator_and_truncated_layouts() { - let mut wrong_owner = index_account(); - wrong_owner.owner = Pubkey::new_unique(); - assert!(index_trader_state_range(&wrong_owner, &FIRST_KEY).is_err()); - - let mut wrong_discriminator = index_account(); - wrong_discriminator.data[..8].fill(0); - assert!(index_trader_state_range(&wrong_discriminator, &FIRST_KEY).is_err()); - - for len in [0, 79, 95, 96 + 3 * 64 - 1] { - let mut truncated = index_account(); - truncated.data.truncate(len); - assert!(index_trader_state_range(&truncated, &FIRST_KEY).is_err()); + assert!(index_trader_state_range(&index, &[44; 32]).is_err()); + + type Corrupt = fn(&mut Account); + let corruptions: [(&str, Corrupt); 8] = [ + ("only a freed duplicate matches", |index| { + index.data[112..144].copy_from_slice(&[33; 32]) + }), + ("cycle", |index| write_u32(&mut index.data, 96, 2)), + ("size disagrees with the reachable nodes", |index| { + write_u32(&mut index.data, 48, 3) + }), + ("child beyond capacity", |index| { + write_u32(&mut index.data, 60, 100); + write_u32(&mut index.data, 164, 4); + }), + ("child above the bump index", |index| { + write_u32(&mut index.data, 60, 2) + }), + ("duplicate reachable key", |index| { + index.data[176..208].copy_from_slice(&FIRST_KEY) + }), + ("wrong owner", |index| index.owner = Pubkey::new_unique()), + ("wrong discriminator", |index| index.data[..8].fill(0)), + ]; + for (case, corrupt) in corruptions { + let mut index = index_account(); + corrupt(&mut index); + assert!( + index_trader_state_range(&index, &FIRST_KEY).is_err(), + "{case}" + ); } - } - - #[test] - fn index_lookup_rejects_unsupported_arena_counts() { for (arenas, active) in [(0_u16, 1_u16), (2, 1), (1, 0), (1, 2)] { let mut index = index_account(); index.data[52..54].copy_from_slice(&arenas.to_le_bytes()); index.data[54..56].copy_from_slice(&active.to_le_bytes()); assert!(index_trader_state_range(&index, &FIRST_KEY).is_err()); } + for len in [0, 79, 95, 96 + 3 * 64 - 1] { + let mut index = index_account(); + index.data.truncate(len); + assert!(index_trader_state_range(&index, &FIRST_KEY).is_err()); + } } #[test] @@ -334,20 +317,6 @@ mod tests { assert_eq!(stale_low, trader_account(FIRST_KEY, 1, true)); } - #[test] - fn cold_trader_keeps_standalone_collateral_and_increase_guard() { - let trader = Pubkey::new_from_array(FIRST_KEY); - let account = trader_account(FIRST_KEY, 50, false); - let header = trader_header(&trader, &account).unwrap(); - assert_eq!(effective_collateral(&header, None).unwrap(), 50); - assert_eq!( - effective_collateral(&header, Some(&index_account())).unwrap(), - 50 - ); - assert!(build_phoenix_collateral_scenario(trader, &account, "51", None).is_err()); - assert!(build_phoenix_collateral_scenario(trader, &account, "50", None).is_ok()); - } - fn global_account(index: &Pubkey) -> Account { use super::super::state_builder::PHOENIX_GLOBAL_CONFIG; @@ -368,21 +337,9 @@ mod tests { use super::super::state_builder::PHOENIX_GLOBAL_CONFIG; use crate::surfnet::svm::SurfnetSvm; - for (key, other_key, collateral_offset, field, target) in [ - ( - FIRST_KEY, - SECOND_KEY, - 144, - "traderState.quoteLotCollateral", - 1_i64, - ), - ( - SECOND_KEY, - FIRST_KEY, - 208, - "traderState.quoteLotCollateral", - -9_007_199_254_740_993, - ), + for (key, other_key, collateral_offset, target) in [ + (FIRST_KEY, SECOND_KEY, 144, 1_i64), + (SECOND_KEY, FIRST_KEY, 208, -9_007_199_254_740_993), ] { let trader = Pubkey::new_from_array(key); let other_trader = Pubkey::new_from_array(other_key); @@ -394,17 +351,13 @@ mod tests { let mut before_index = index_account(); before_index.lamports = 1; let global = global_account(&index_key); - let mut scenario = build_phoenix_collateral_scenario( + let scenario = build_phoenix_collateral_scenario( trader, &before_trader, &target.to_string(), Some(&before_index), ) .unwrap(); - scenario.overrides[0].values = std::collections::HashMap::from([( - field.to_string(), - serde_json::json!(target.to_string()), - )]); let (mut svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); svm.set_account(&trader, before_trader.clone()).unwrap(); svm.set_account(&other_trader, before_other.clone()) @@ -507,41 +460,29 @@ mod tests { } } - #[test] - fn hot_trader_fields_allow_only_the_mirrored_collateral() { - let collateral_only = HashMap::from([( - "traderState.quoteLotCollateral".to_string(), - serde_json::json!("1"), - )]); - assert!(validate_hot_trader_fields(&collateral_only).is_ok()); - for field in ["traderState.flags", "traderState"] { - let values = HashMap::from([(field.to_string(), serde_json::json!(0))]); - let error = validate_hot_trader_fields(&values).unwrap_err().to_string(); - assert!( - error.contains(field) && error.contains("hot Traders"), - "{error}" - ); - } - } - #[tokio::test] - async fn materialization_skips_a_cold_trader_whose_header_key_mismatches() { + async fn materialization_patches_a_cold_trader_and_skips_a_mismatched_header_key() { use crate::surfnet::svm::SurfnetSvm; let trader = Pubkey::new_from_array(FIRST_KEY); let matching = trader_account(FIRST_KEY, 9_999, false); - let scenario = build_phoenix_collateral_scenario(trader, &matching, "1", None).unwrap(); - let mut on_chain = trader_account(SECOND_KEY, 9_999, false); - on_chain.lamports = 1; - let (mut svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); - svm.set_account(&trader, on_chain.clone()).unwrap(); - svm.register_scenario(scenario, Some(100)).unwrap(); - - svm.materialize_overrides_for_slot(&None, 100) - .await - .unwrap(); + for (on_chain_key, patched) in [(FIRST_KEY, true), (SECOND_KEY, false)] { + let scenario = build_phoenix_collateral_scenario(trader, &matching, "1", None).unwrap(); + let mut on_chain = trader_account(on_chain_key, 9_999, false); + on_chain.lamports = 1; + let (mut svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); + svm.set_account(&trader, on_chain.clone()).unwrap(); + svm.register_scenario(scenario, Some(100)).unwrap(); + svm.materialize_overrides_for_slot(&None, 100) + .await + .unwrap(); - assert_eq!(svm.get_account(&trader).unwrap().unwrap(), on_chain); + let mut expected = on_chain; + if patched { + expected.data[88..96].copy_from_slice(&1_i64.to_le_bytes()); + } + assert_eq!(svm.get_account(&trader).unwrap().unwrap(), expected); + } } #[test] @@ -579,22 +520,4 @@ mod tests { ); } } - - #[test] - fn trader_header_rejects_an_embedded_key_for_another_account() { - let trader = Pubkey::new_from_array(FIRST_KEY); - for hot in [false, true] { - let account = trader_account(SECOND_KEY, 9_999, hot); - let error = trader_header(&trader, &account).unwrap_err(); - assert!( - error - .to_string() - .contains("key does not match its account address") - ); - assert!( - build_phoenix_collateral_scenario(trader, &account, "1", Some(&index_account())) - .is_err() - ); - } - } } diff --git a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs index 8bfacafab..79e60db84 100644 --- a/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs +++ b/crates/core/src/scenarios/protocols/phoenix-eternal/v1/state_builder.rs @@ -55,25 +55,19 @@ pub fn phoenix_market_symbols( None::, )); } - - let map = PerpAssetMap::try_from_account_bytes(&account.data).map_err(|error| { + let invalid = |error: PhoenixAccountDecodeError| { SurfpoolError::invalid_account_data( perp_asset_map, "Expected a valid Phoenix Eternal PerpAssetMap account", Some(error), ) - })?; + }; + let map = PerpAssetMap::try_from_account_bytes(&account.data).map_err(invalid)?; let mut symbols = map .iter() .map(|entry| entry.map(|entry| entry.symbol.as_str().to_string())) .collect::, _>>() - .map_err(|error| { - SurfpoolError::invalid_account_data( - perp_asset_map, - "Expected a valid Phoenix Eternal PerpAssetMap account", - Some(error), - ) - })?; + .map_err(invalid)?; symbols.sort_unstable(); Ok(symbols) @@ -81,8 +75,6 @@ pub fn phoenix_market_symbols( #[derive(Debug, Error, PartialEq, Eq)] enum PhoenixPricePatchError { - #[error("expected Phoenix Eternal owner, got {actual}")] - InvalidOwner { actual: Pubkey }, #[error("invalid Phoenix PerpAssetMap account: {0}")] InvalidPerpAssetMap(#[from] PhoenixAccountDecodeError), #[error("Phoenix market {symbol} was not found")] @@ -111,15 +103,11 @@ fn changed_byte_outside( } fn patch_direct_mark( - owner: &Pubkey, data: &[u8], symbol: &str, target_ticks: u64, mark_slot: u64, ) -> Result, PhoenixPricePatchError> { - if owner != &PHOENIX_ETERNAL_PROGRAM_ID { - return Err(PhoenixPricePatchError::InvalidOwner { actual: *owner }); - } let target_ticks = Ticks::new_checked(target_ticks).map_err(|_| PhoenixPricePatchError::InvalidTicks { ticks: target_ticks, @@ -131,16 +119,12 @@ fn patch_direct_mark( } fn patch_reference_prices( - owner: &Pubkey, data: &[u8], symbol: &str, spot_ticks: u64, perp_ticks: u64, reference_slot: u64, ) -> Result, PhoenixPricePatchError> { - if owner != &PHOENIX_ETERNAL_PROGRAM_ID { - return Err(PhoenixPricePatchError::InvalidOwner { actual: *owner }); - } let spot_ticks = Ticks::new_checked(spot_ticks) .map_err(|_| PhoenixPricePatchError::InvalidTicks { ticks: spot_ticks })?; let perp_ticks = Ticks::new_checked(perp_ticks) @@ -218,29 +202,13 @@ fn reference_value_ranges() -> Vec> { } fn unique_subslice_offset(data: &[u8], needle: &[u8]) -> Option { - if needle.is_empty() || needle.len() > data.len() { - return None; - } - - let mut match_offset = None; - let mut search_start = 0; - while search_start + needle.len() <= data.len() { - let Some(relative_offset) = data[search_start..=data.len() - needle.len()] - .iter() - .position(|byte| *byte == needle[0]) - else { - break; - }; - let offset = search_start + relative_offset; - if data[offset..].starts_with(needle) { - if match_offset.is_some() { - return None; - } - match_offset = Some(offset); - } - search_start = offset + 1; - } - match_offset + let mut matches = data + .windows(needle.len()) + .enumerate() + .filter(|(_, window)| *window == needle) + .map(|(offset, _)| offset); + let offset = matches.next()?; + matches.next().is_none().then_some(offset) } pub fn forge_phoenix_override( @@ -249,73 +217,52 @@ pub fn forge_phoenix_override( account_values: &HashMap, materialization_slot: u64, ) -> SurfpoolResult> { - let wants_direct_mark = account_values.contains_key(DIRECT_MARK_TICKS_FIELD); - let wants_reference = account_values.contains_key(REFERENCE_SPOT_TICKS_FIELD) - || account_values.contains_key(REFERENCE_PERP_TICKS_FIELD); - let fields: &[&str] = match (wants_direct_mark, wants_reference) { - (true, false) => &[DIRECT_MARK_SYMBOL_FIELD, DIRECT_MARK_TICKS_FIELD], - (false, true) => &[ - DIRECT_MARK_SYMBOL_FIELD, - REFERENCE_SPOT_TICKS_FIELD, + if account.owner != PHOENIX_ETERNAL_PROGRAM_ID { + return Err(SurfpoolError::invalid_account_owner( + account_pubkey, + None::, + )); + } + let mut fields: Vec<&str> = account_values.keys().map(String::as_str).collect(); + fields.sort_unstable(); + let symbol = || { + account_values[DIRECT_MARK_SYMBOL_FIELD] + .as_str() + .filter(|value| !value.is_empty()) + .ok_or_else(|| SurfpoolError::internal("symbol must be a non-empty string")) + }; + let ticks = |field: &str| parse_unsigned_ticks(&account_values[field], field); + let patched = match fields.as_slice() { + [DIRECT_MARK_SYMBOL_FIELD, DIRECT_MARK_TICKS_FIELD] => patch_direct_mark( + &account.data, + symbol()?, + ticks(DIRECT_MARK_TICKS_FIELD)?, + materialization_slot, + ), + [ REFERENCE_PERP_TICKS_FIELD, - ], + REFERENCE_SPOT_TICKS_FIELD, + DIRECT_MARK_SYMBOL_FIELD, + ] => patch_reference_prices( + &account.data, + symbol()?, + ticks(REFERENCE_SPOT_TICKS_FIELD)?, + ticks(REFERENCE_PERP_TICKS_FIELD)?, + materialization_slot, + ), _ => { return Err(SurfpoolError::internal( "Phoenix map overrides accept exactly one value group: \ - symbol + target_ticks, or symbol + spot_ticks + perp_ticks", + symbol + target_ticks, or symbol + spot_ticks + perp_ticks", )); } }; - if account_values.len() != fields.len() - || fields - .iter() - .any(|field| !account_values.contains_key(*field)) - { - return Err(SurfpoolError::internal(format!( - "Phoenix map overrides accept only {}", - fields.join(", ") - ))); - } - let symbol = account_values[DIRECT_MARK_SYMBOL_FIELD] - .as_str() - .filter(|value| !value.is_empty()) - .ok_or_else(|| SurfpoolError::internal("symbol must be a non-empty string"))?; - let patched = if wants_direct_mark { - patch_direct_mark( - &account.owner, - &account.data, - symbol, - parse_unsigned_ticks( - &account_values[DIRECT_MARK_TICKS_FIELD], - DIRECT_MARK_TICKS_FIELD, - )?, - materialization_slot, - ) - } else { - patch_reference_prices( - &account.owner, - &account.data, - symbol, - parse_unsigned_ticks( - &account_values[REFERENCE_SPOT_TICKS_FIELD], - REFERENCE_SPOT_TICKS_FIELD, - )?, - parse_unsigned_ticks( - &account_values[REFERENCE_PERP_TICKS_FIELD], - REFERENCE_PERP_TICKS_FIELD, - )?, - materialization_slot, - ) - }; - patched.map_err(|error| match error { - PhoenixPricePatchError::InvalidOwner { .. } => { - SurfpoolError::invalid_account_owner(account_pubkey, Some(error)) - } - _ => SurfpoolError::invalid_account_data( + patched.map_err(|error| { + SurfpoolError::invalid_account_data( account_pubkey, "Expected a valid Phoenix Eternal PerpAssetMap account", Some(error), - ), + ) }) } @@ -325,7 +272,8 @@ pub fn build_phoenix_collateral_scenario( target_quote_lots: &str, global_trader_index: Option<&Account>, ) -> SurfpoolResult { - let target_quote_lots = parse_quote_lot_collateral(target_quote_lots)?; + let target_quote_lots = + super::collateral::parse_quote_lot_collateral(&serde_json::json!(target_quote_lots))?; let header = super::collateral::trader_header(&trader, trader_account)?; // Raising collateral needs a real deposit into the global vault. @@ -416,16 +364,8 @@ fn parse_unsigned_ticks(value: &serde_json::Value, field: &str) -> SurfpoolResul }) } -fn parse_quote_lot_collateral(value: &str) -> SurfpoolResult { - value.parse::().map_err(|_| { - SurfpoolError::internal( - "targetQuoteLots must be a signed 64-bit integer encoded as a string", - ) - }) -} - #[cfg(test)] -pub(crate) mod tests { +mod tests { use base64::{Engine, prelude::BASE64_STANDARD}; use phoenix_rise_accounts::{PhoenixAccount, trader::TraderHeader}; use solana_account::Account; @@ -476,7 +416,7 @@ pub(crate) mod tests { account } - pub(crate) fn perp_asset_map_fixture() -> Vec { + fn perp_asset_map_fixture() -> Vec { let prefix = BASE64_STANDARD .decode(SOL_PERP_ASSET_MAP_PREFIX_B64) .unwrap(); @@ -488,165 +428,167 @@ pub(crate) mod tests { data } - #[test] - fn builder_rejects_an_account_that_is_not_a_trader() { - let mut account = trader_account(); - account.data[..8].fill(0); - - let error = build_phoenix_collateral_scenario(Pubkey::new_unique(), &account, "1", None) - .unwrap_err(); - - assert!(error.to_string().contains("Trader")); - } - - #[test] - fn builder_rejects_collateral_outside_the_signed_64_bit_range() { - let error = build_phoenix_collateral_scenario( - Pubkey::new_unique(), - &trader_account(), - "9223372036854775808", - None, - ) - .unwrap_err(); - - assert!(error.to_string().contains("signed 64-bit integer")); + fn perp_asset_map_account() -> Account { + Account { + lamports: 1, + data: perp_asset_map_fixture(), + owner: PHOENIX_ETERNAL_PROGRAM_ID, + executable: false, + rent_epoch: 0, + } } #[test] - fn builds_one_editable_collateral_override_for_the_requested_trader() { - let trader = Pubkey::new_unique(); - let preparation = build_phoenix_collateral_scenario( - trader, - &trader_account_for(trader, 0), - "-9007199254740993", - None, - ) - .unwrap(); - - assert_eq!(preparation.overrides.len(), 1); - - let collateral_override = &preparation.overrides[0]; - assert_eq!( - collateral_override.template_id, - "phoenix-trader-collateral-stress" - ); - assert_eq!( - collateral_override.account, - AccountAddress::Pubkey(trader.to_string()) - ); - assert_eq!( - collateral_override.values[COLLATERAL_FIELD], - serde_json::json!("-9007199254740993") - ); - assert_eq!(collateral_override.scenario_relative_slot, PREPARATION_SLOT); - assert!(!collateral_override.fetch_before_use); + fn builder_rejects_non_traders_and_out_of_range_collateral() { + let mut not_a_trader = trader_account(); + not_a_trader.data[..8].fill(0); + let mut foreign = trader_account(); + foreign.owner = Pubkey::new_unique(); + for (account, target, expected) in [ + (not_a_trader, "1", "Trader"), + (foreign, "1", "invalid account owner"), + ( + trader_account(), + "9223372036854775808", + "signed 64-bit integer", + ), + ] { + let error = + build_phoenix_collateral_scenario(Pubkey::new_unique(), &account, target, None) + .unwrap_err(); + assert!(error.to_string().contains(expected), "{error}"); + } } #[test] - fn builder_refuses_to_raise_collateral_above_its_vault_backing() { + fn builds_one_collateral_override_bounded_by_its_vault_backing() { let trader = Pubkey::new_unique(); let funded = trader_account_for(trader, 500); - let raised = build_phoenix_collateral_scenario(trader, &funded, "501", None).unwrap_err(); assert!(raised.to_string().contains("can only lower collateral")); - let lowered = build_phoenix_collateral_scenario(trader, &funded, "499", None).unwrap(); - assert_eq!(lowered.overrides[0].values[COLLATERAL_FIELD], "499"); - let held = build_phoenix_collateral_scenario(trader, &funded, "500", None).unwrap(); - assert_eq!(held.overrides[0].values[COLLATERAL_FIELD], "500"); - } - - #[test] - fn builder_rejects_a_non_phoenix_account() { - let trader = Pubkey::new_unique(); - let mut account = trader_account(); - account.owner = Pubkey::new_unique(); - - let error = build_phoenix_collateral_scenario(trader, &account, "1", None).unwrap_err(); - - assert!(error.to_string().contains("invalid account owner")); + for target in ["500", "-9007199254740993"] { + let preparation = + build_phoenix_collateral_scenario(trader, &funded, target, None).unwrap(); + assert_eq!(preparation.overrides.len(), 1); + let collateral_override = &preparation.overrides[0]; + assert_eq!( + collateral_override.template_id, + "phoenix-trader-collateral-stress" + ); + assert_eq!( + collateral_override.account, + AccountAddress::Pubkey(trader.to_string()) + ); + assert_eq!( + collateral_override.values[COLLATERAL_FIELD], + serde_json::json!(target) + ); + assert_eq!(collateral_override.scenario_relative_slot, PREPARATION_SLOT); + assert!(!collateral_override.fetch_before_use); + } } #[test] fn lists_active_market_symbols_from_the_perp_asset_map() { - let perp_asset_map = Pubkey::new_unique(); - let account = Account { - lamports: 1, - data: perp_asset_map_fixture(), - owner: PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }; - assert_eq!( - phoenix_market_symbols(perp_asset_map, &account).unwrap(), + phoenix_market_symbols(Pubkey::new_unique(), &perp_asset_map_account()).unwrap(), vec!["SOL"] ); } #[test] - fn patches_only_the_selected_mark_ticks_and_slot_in_an_official_market_entry() { - let data = perp_asset_map_fixture(); - let map = PerpAssetMap::try_from_account_bytes(&data).unwrap(); - let before = map.find_by_symbol("SOL").unwrap().unwrap(); - let metadata_offset = unique_subslice_offset(&data, before.metadata.as_bytes()).unwrap(); - - let patched = patch_direct_mark(&PHOENIX_ETERNAL_PROGRAM_ID, &data, "SOL", 1, 123).unwrap(); - let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); - let after = map.find_by_symbol("SOL").unwrap().unwrap(); - assert_eq!(after.metadata.oracle_price().mark_price.price.slot, 123); + fn direct_mark_patches_only_the_selected_mark_ticks_and_slot() { + let account = perp_asset_map_account(); + let before = PerpAssetMap::try_from_account_bytes(&account.data) + .unwrap() + .find_by_symbol("SOL") + .unwrap() + .unwrap(); + let metadata_offset = + unique_subslice_offset(&account.data, before.metadata.as_bytes()).unwrap(); + let values = HashMap::from([ + ( + DIRECT_MARK_SYMBOL_FIELD.to_string(), + serde_json::json!("SOL"), + ), + (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), + ]); - assert_eq!( - after - .metadata - .oracle_price() - .mark_price - .price - .ticks - .as_inner(), - 1 - ); - assert_eq!(patched.len(), data.len()); + let patched = + forge_phoenix_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); + let after = PerpAssetMap::try_from_account_bytes(&patched) + .unwrap() + .find_by_symbol("SOL") + .unwrap() + .unwrap(); + let price = after.metadata.oracle_price().mark_price.price; + assert_eq!((price.ticks.as_inner(), price.slot), (1, 123)); + assert_eq!(patched.len(), account.data.len()); + let allowed = + metadata_offset + MARK_PRICE_RANGE.start..metadata_offset + MARK_PRICE_RANGE.end; assert!( - data.iter() + account + .data + .iter() .zip(&patched) .enumerate() .filter(|(_, (before, after))| before != after) - .all(|(offset, _)| { - (metadata_offset + MARK_PRICE_RANGE.start - ..metadata_offset + MARK_PRICE_RANGE.end) - .contains(&offset) - }) + .all(|(offset, _)| allowed.contains(&offset)) + ); + + let mut numeric_ticks = values; + numeric_ticks.insert(DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!(1)); + assert!( + forge_phoenix_override(&Pubkey::new_unique(), &account, &numeric_ticks, 123) + .unwrap_err() + .to_string() + .contains("encoded as a string") ); } #[test] - fn reference_price_patch_supports_both_divergence_directions_and_preserves_mark() { - let data = perp_asset_map_fixture(); - let before_map = PerpAssetMap::try_from_account_bytes(&data).unwrap(); - let before = before_map.find_by_symbol("SOL").unwrap().unwrap(); - let before_mark = before + fn reference_prices_patch_both_directions_and_preserve_the_mark() { + let account = perp_asset_map_account(); + let before_mark = PerpAssetMap::try_from_account_bytes(&account.data) + .unwrap() + .find_by_symbol("SOL") + .unwrap() + .unwrap() .metadata .oracle_price() .mark_price .price .ticks .as_inner(); + let values = |spot_ticks: u64, perp_ticks: u64| { + HashMap::from([ + ( + DIRECT_MARK_SYMBOL_FIELD.to_string(), + serde_json::json!("SOL"), + ), + ( + REFERENCE_SPOT_TICKS_FIELD.to_string(), + serde_json::json!(spot_ticks.to_string()), + ), + ( + REFERENCE_PERP_TICKS_FIELD.to_string(), + serde_json::json!(perp_ticks.to_string()), + ), + ]) + }; for (spot_ticks, perp_ticks) in [(8_000, 7_000), (7_000, 8_000)] { - let patched = patch_reference_prices( - &PHOENIX_ETERNAL_PROGRAM_ID, - &data, - "SOL", - spot_ticks, - perp_ticks, + let patched = forge_phoenix_override( + &Pubkey::new_unique(), + &account, + &values(spot_ticks, perp_ticks), 123, ) .unwrap(); let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); let entry = map.find_by_symbol("SOL").unwrap().unwrap(); let price = entry.metadata.oracle_price(); - assert_eq!(price.mark_price.price.ticks.as_inner(), before_mark); assert!( price @@ -664,118 +606,10 @@ pub(crate) mod tests { .iter() .all(|value| value.ticks.as_inner() == perp_ticks && value.slot == 123) ); - assert_eq!(patched.len(), data.len()); + assert_eq!(patched.len(), account.data.len()); } - } - - #[test] - fn direct_mark_rejects_unknown_markets_and_out_of_range_ticks() { - let data = perp_asset_map_fixture(); - - assert!(matches!( - patch_direct_mark(&PHOENIX_ETERNAL_PROGRAM_ID, &data, "BTC", 1, 123), - Err(PhoenixPricePatchError::MarketNotFound { .. }) - )); - assert!(matches!( - patch_direct_mark( - &PHOENIX_ETERNAL_PROGRAM_ID, - &data, - "SOL", - u64::from(u32::MAX) + 1, - 123, - ), - Err(PhoenixPricePatchError::InvalidTicks { .. }) - )); - } - #[test] - fn direct_mark_override_requires_exact_string_fields() { - let account = Account { - lamports: 1, - data: perp_asset_map_fixture(), - owner: PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }; - let values = HashMap::from([ - ( - DIRECT_MARK_SYMBOL_FIELD.to_string(), - serde_json::json!("SOL"), - ), - (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), - ]); - - let patched = - forge_phoenix_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); - let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); - let price = map - .find_by_symbol("SOL") - .unwrap() - .unwrap() - .metadata - .oracle_price() - .mark_price - .price; - assert_eq!(price.ticks.as_inner(), 1); - assert_eq!(price.slot, 123); - - let mut numeric_ticks = values; - numeric_ticks.insert(DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!(1)); - assert!( - forge_phoenix_override(&Pubkey::new_unique(), &account, &numeric_ticks, 123) - .unwrap_err() - .to_string() - .contains("encoded as a string") - ); - } - - #[test] - fn reference_override_patches_prices_and_requires_string_fields() { - let account = Account { - lamports: 1, - data: perp_asset_map_fixture(), - owner: PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }; - let values = HashMap::from([ - ( - DIRECT_MARK_SYMBOL_FIELD.to_string(), - serde_json::json!("SOL"), - ), - ( - REFERENCE_SPOT_TICKS_FIELD.to_string(), - serde_json::json!("8000"), - ), - ( - REFERENCE_PERP_TICKS_FIELD.to_string(), - serde_json::json!("7000"), - ), - ]); - - let patched = - forge_phoenix_override(&Pubkey::new_unique(), &account, &values, 123).unwrap(); - let map = PerpAssetMap::try_from_account_bytes(&patched).unwrap(); - let entry = map.find_by_symbol("SOL").unwrap().unwrap(); - let price = entry.metadata.oracle_price(); - - assert!( - price - .mark_price - .spot_price_component - .last_exchange_spot_price - .iter() - .all(|value| value.slot == 123 && value.ticks.as_inner() == 8_000) - ); - assert!( - price - .mark_price - .perp_price_component - .last_exchange_perp_price - .iter() - .all(|value| value.slot == 123 && value.ticks.as_inner() == 7_000) - ); - let mut numeric = values; + let mut numeric = values(8_000, 7_000); numeric.insert( REFERENCE_SPOT_TICKS_FIELD.to_string(), serde_json::json!(8000), @@ -789,31 +623,41 @@ pub(crate) mod tests { } #[test] - fn forge_rejects_no_value_group() { - let error = forge_phoenix_override( - &Pubkey::new_unique(), - &trader_account(), - &HashMap::new(), - 100, - ) - .unwrap_err(); - - assert!(error.to_string().contains("exactly one value group")); + fn direct_mark_rejects_unknown_markets_and_out_of_range_ticks() { + let data = perp_asset_map_fixture(); + assert!(matches!( + patch_direct_mark(&data, "BTC", 1, 123), + Err(PhoenixPricePatchError::MarketNotFound { .. }) + )); + assert!(matches!( + patch_direct_mark(&data, "SOL", u64::from(u32::MAX) + 1, 123), + Err(PhoenixPricePatchError::InvalidTicks { .. }) + )); } #[test] - fn forge_rejects_mixed_value_groups() { - let values = HashMap::from([ - (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), - ( - REFERENCE_SPOT_TICKS_FIELD.to_string(), - serde_json::json!("2"), - ), - ]); - - let error = forge_phoenix_override(&Pubkey::new_unique(), &trader_account(), &values, 100) + fn forge_rejects_missing_or_mixed_value_groups() { + for values in [ + HashMap::new(), + HashMap::from([ + (DIRECT_MARK_TICKS_FIELD.to_string(), serde_json::json!("1")), + ( + REFERENCE_SPOT_TICKS_FIELD.to_string(), + serde_json::json!("2"), + ), + ]), + ] { + let error = forge_phoenix_override( + &Pubkey::new_unique(), + &perp_asset_map_account(), + &values, + 100, + ) .unwrap_err(); - - assert!(error.to_string().contains("exactly one value group")); + assert!( + error.to_string().contains("exactly one value group"), + "{error}" + ); + } } } diff --git a/crates/core/src/scenarios/registry.rs b/crates/core/src/scenarios/registry.rs index 0813be859..ff19fdbbb 100644 --- a/crates/core/src/scenarios/registry.rs +++ b/crates/core/src/scenarios/registry.rs @@ -473,23 +473,6 @@ mod tests { } } - #[test] - fn phoenix_idl_contains_only_real_accounts_with_unique_discriminators() { - let idl: surfpool_types::Idl = serde_json::from_str(PHOENIX_ETERNAL_IDL_CONTENT).unwrap(); - let mut discriminators = std::collections::HashSet::new(); - for account in &idl.accounts { - assert!( - discriminators.insert(&account.discriminator), - "duplicate {}", - account.name - ); - } - for name in ["DirectMarkOverride", "ReferencePriceOverride"] { - assert!(!idl.accounts.iter().any(|account| account.name == name)); - assert!(!idl.types.iter().any(|definition| definition.name == name)); - } - } - #[test] fn test_registry_loads_all_protocols() { let registry = TemplateRegistry::new(); diff --git a/crates/core/src/surfnet/svm.rs b/crates/core/src/surfnet/svm.rs index 27b134e10..e993fce99 100644 --- a/crates/core/src/surfnet/svm.rs +++ b/crates/core/src/surfnet/svm.rs @@ -95,7 +95,7 @@ use crate::{ TemplateRegistry, protocols::phoenix_eternal::v1::{ collateral::{ - collateral_override_value, trader_header, validate_collateral_fields, + parse_quote_lot_collateral, trader_header, validate_collateral_fields, validate_hot_trader_fields, }, state_builder::{ @@ -2947,32 +2947,22 @@ impl SurfnetSvm { let is_phoenix_trader = account.owner() == &PHOENIX_ETERNAL_PROGRAM_ID && is_phoenix_trader_account(account.data()); if is_phoenix_trader { - let header = match trader_header(&account_pubkey, &account) { - Ok(header) => header, - Err(error) => { - warn!("Skipping override {}: {}", override_instance.id, error); - continue; + let checked = trader_header(&account_pubkey, &account).and_then(|header| { + validate_collateral_fields(&account_values)?; + if header.trader_state.is_hot() { + validate_hot_trader_fields(&account_values)?; } - }; - if let Err(error) = validate_collateral_fields(&account_values) { + if let Some(value) = + account_values.get_mut("traderState.quoteLotCollateral") + { + *value = serde_json::Value::from(parse_quote_lot_collateral(value)?); + } + Ok(()) + }); + if let Err(error) = checked { warn!("Skipping override {}: {}", override_instance.id, error); continue; } - if header.trader_state.is_hot() { - if let Err(error) = validate_hot_trader_fields(&account_values) { - warn!("Skipping override {}: {}", override_instance.id, error); - continue; - } - } - if let Some(value) = account_values.get_mut("traderState.quoteLotCollateral") { - match collateral_override_value(value) { - Ok(parsed) => *value = parsed, - Err(e) => { - warn!("Skipping override {}: {}", override_instance.id, e); - continue; - } - } - } } // Mints fail the token unpack and keep flowing through the IDL path. diff --git a/crates/core/src/tests/phoenix/mod.rs b/crates/core/src/tests/phoenix/mod.rs index 9590edbf6..7415c88fc 100644 --- a/crates/core/src/tests/phoenix/mod.rs +++ b/crates/core/src/tests/phoenix/mod.rs @@ -329,10 +329,12 @@ async fn collateral_idl_override_preserves_live_trader_layout() { let mut overrides = HashMap::new(); overrides.insert( "traderState.quoteLotCollateral".to_string(), - crate::scenarios::protocols::phoenix_eternal::v1::collateral::collateral_override_value( - &serde_json::Value::String(target.to_string()), - ) - .unwrap(), + serde_json::Value::from( + crate::scenarios::protocols::phoenix_eternal::v1::collateral::parse_quote_lot_collateral( + &serde_json::json!(target.to_string()), + ) + .unwrap(), + ), ); let forged = svm @@ -524,9 +526,13 @@ async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { Some(&index_account), ) .unwrap(); - let mut shock = phoenix_direct_mark_scenario(graph.perp_asset_map, &symbol, "1", false) - .overrides - .remove(0); + let mut shock = phoenix_market_scenario( + "phoenix-direct-mark-risk-shock", + graph.perp_asset_map, + &[("symbol", symbol.as_str()), ("target_ticks", "1")], + ) + .overrides + .remove(0); shock.scenario_relative_slot = 1; cascade.add_override(shock); mark_locker @@ -561,24 +567,6 @@ async fn phoenix_state_preparation_changes_hawkeye_risk_outcomes() { after_mark.mark_price_ticks, 1, "stage 1 shocks the mark the program itself reads" ); - - // The second live market proves the preparations are not market-specific. - let (second_locker, graph) = phoenix_behavior_locker().await; - let (symbol, orderbook, spline) = graph.markets[1].clone(); - let before_second = hawkeye_bbo_for_market(&graph, &second_locker, orderbook, spline); - second_locker - .register_scenario( - phoenix_direct_mark_scenario(graph.perp_asset_map, &symbol, "1", false), - Some(graph.clock.slot), - ) - .unwrap(); - second_locker - .materialize_overrides_for_slot(&None, graph.clock.slot) - .await - .unwrap(); - let after_second = hawkeye_bbo_for_market(&graph, &second_locker, orderbook, spline); - assert_ne!(before_second.mark_price_ticks, 1); - assert_eq!(after_second.mark_price_ticks, 1); } #[tokio::test(flavor = "multi_thread")] @@ -589,12 +577,14 @@ async fn reference_prices_change_hawkeye_index_on_live_markets() { let before = hawkeye_bbo_for_market(&graph, &locker, *orderbook, *spline); locker .register_scenario( - phoenix_reference_price_scenario( + phoenix_market_scenario( + "phoenix-reference-price-divergence", graph.perp_asset_map, - symbol, - "80000", - "79000", - false, + &[ + ("symbol", symbol.as_str()), + ("spot_ticks", "80000"), + ("perp_ticks", "79000"), + ], ), Some(graph.clock.slot), ) @@ -841,254 +831,27 @@ const HAWKEYE_MARGIN_RETURN_MAGIC: u64 = 0x955f5b9d3dff253f; const HAWKEYE_BBO_RETURN_MAGIC: u64 = 0xefca1fa31fa74171; -fn phoenix_collateral_scenario( - trader: Pubkey, - collateral: serde_json::Value, - fetch_before_use: bool, -) -> surfpool_types::Scenario { - let mut scenario = surfpool_types::Scenario::new( - "Phoenix collateral stress".to_string(), - "Phoenix Trader collateral override".to_string(), - ); - let mut instance = surfpool_types::OverrideInstance::new( - "phoenix-trader-collateral-stress".to_string(), - 0, - surfpool_types::AccountAddress::Pubkey(trader.to_string()), - ) - .with_values(HashMap::from([( - "traderState.quoteLotCollateral".to_string(), - collateral, - )])); - instance.fetch_before_use = fetch_before_use; - scenario.add_override(instance); - scenario -} - -fn phoenix_direct_mark_scenario( - perp_asset_map: Pubkey, - symbol: &str, - target_ticks: &str, - fetch_before_use: bool, -) -> surfpool_types::Scenario { - let mut scenario = surfpool_types::Scenario::new( - "Phoenix direct mark risk shock".to_string(), - "Phoenix direct mark override".to_string(), - ); - let mut instance = surfpool_types::OverrideInstance::new( - "phoenix-direct-mark-risk-shock".to_string(), - 0, - surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()), - ) - .with_values(HashMap::from([ - ("symbol".to_string(), serde_json::json!(symbol)), - ("target_ticks".to_string(), serde_json::json!(target_ticks)), - ])); - instance.fetch_before_use = fetch_before_use; - scenario.add_override(instance); - scenario -} - -fn phoenix_reference_price_scenario( +fn phoenix_market_scenario( + template_id: &str, perp_asset_map: Pubkey, - symbol: &str, - spot_ticks: &str, - perp_ticks: &str, - fetch_before_use: bool, + values: &[(&str, &str)], ) -> surfpool_types::Scenario { let mut scenario = surfpool_types::Scenario::new( - "Phoenix spot/perp reference divergence".to_string(), - "Phoenix reference-price override".to_string(), + template_id.to_string(), + "Phoenix market override".to_string(), + ); + scenario.add_override( + surfpool_types::OverrideInstance::new( + template_id.to_string(), + 0, + surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()), + ) + .with_values( + values + .iter() + .map(|(field, value)| (field.to_string(), serde_json::Value::from(*value))) + .collect(), + ), ); - let mut instance = surfpool_types::OverrideInstance::new( - "phoenix-reference-price-divergence".to_string(), - 0, - surfpool_types::AccountAddress::Pubkey(perp_asset_map.to_string()), - ) - .with_values(HashMap::from([ - ("symbol".to_string(), serde_json::json!(symbol)), - ("spot_ticks".to_string(), serde_json::json!(spot_ticks)), - ("perp_ticks".to_string(), serde_json::json!(perp_ticks)), - ])); - instance.fetch_before_use = fetch_before_use; - scenario.add_override(instance); scenario } - -#[tokio::test(flavor = "multi_thread")] -async fn materialize_patches_only_phoenix_trader_collateral() { - let trader = Pubkey::new_unique(); - let mut base = phoenix_trader_fixture(6_996_825_500); - base[24..56].copy_from_slice(trader.as_ref()); - let scenario = phoenix_collateral_scenario(trader, serde_json::json!("371499999"), false); - let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); - let locker = SurfnetSvmLocker::new(svm); - locker.with_svm_writer(|svm_writer| { - svm_writer - .set_account( - &trader, - Account { - lamports: 1, - data: base.clone(), - owner: PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }, - ) - .unwrap(); - }); - - locker.register_scenario(scenario, Some(100)).unwrap(); - locker - .materialize_overrides_for_slot(&None, 100) - .await - .unwrap(); - - let after = locker - .with_svm_reader(|svm_reader| svm_reader.get_account(&trader)) - .unwrap() - .unwrap(); - let header = TraderHeader::try_read_from_account_bytes(&after.data).unwrap(); - assert_eq!( - header.trader_state.quote_lot_collateral.as_inner(), - 371_499_999 - ); - assert_eq!(after.data.len(), base.len()); - assert!( - base.iter() - .zip(&after.data) - .enumerate() - .filter(|(_, (before, after))| before != after) - .all(|(offset, _)| (88..96).contains(&offset)) - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn materialize_applies_a_phoenix_direct_mark_override() { - let perp_asset_map = Pubkey::new_unique(); - let base = crate::scenarios::protocols::phoenix_eternal::v1::state_builder::tests::perp_asset_map_fixture(); - let scenario = phoenix_direct_mark_scenario(perp_asset_map, "SOL", "1", false); - let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); - let locker = SurfnetSvmLocker::new(svm); - locker.with_svm_writer(|svm_writer| { - svm_writer - .set_account( - &perp_asset_map, - Account { - lamports: 1, - data: base.clone(), - owner: PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }, - ) - .unwrap(); - }); - - locker.register_scenario(scenario, Some(100)).unwrap(); - locker - .materialize_overrides_for_slot(&None, 100) - .await - .unwrap(); - - let after = locker - .with_svm_reader(|svm_reader| svm_reader.get_account(&perp_asset_map)) - .unwrap() - .unwrap(); - let map = PerpAssetMap::try_from_account_bytes(&after.data).unwrap(); - let mark_price = map - .find_by_symbol("SOL") - .unwrap() - .unwrap() - .metadata - .oracle_price() - .mark_price - .price; - assert_eq!(mark_price.ticks.as_inner(), 1); - assert_eq!(mark_price.slot, 100); - assert_eq!(after.data.len(), base.len()); - assert!( - base.iter() - .zip(&after.data) - .filter(|(before, after)| before != after) - .count() - <= 16 - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn materialize_refreshes_phoenix_reference_price_slots() { - let perp_asset_map = Pubkey::new_unique(); - let base = crate::scenarios::protocols::phoenix_eternal::v1::state_builder::tests::perp_asset_map_fixture(); - let before = PerpAssetMap::try_from_account_bytes(&base).unwrap(); - let before_mark = before - .find_by_symbol("SOL") - .unwrap() - .unwrap() - .metadata - .oracle_price() - .mark_price - .price - .ticks - .as_inner(); - let scenario = phoenix_reference_price_scenario(perp_asset_map, "SOL", "8000", "7000", false); - let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default(); - let locker = SurfnetSvmLocker::new(svm); - locker.with_svm_writer(|svm_writer| { - svm_writer - .set_account( - &perp_asset_map, - Account { - lamports: 1, - data: base, - owner: PHOENIX_ETERNAL_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }, - ) - .unwrap(); - }); - - locker.register_scenario(scenario, Some(100)).unwrap(); - locker - .materialize_overrides_for_slot(&None, 100) - .await - .unwrap(); - - let after = locker - .with_svm_reader(|svm_reader| svm_reader.get_account(&perp_asset_map)) - .unwrap() - .unwrap(); - let map = PerpAssetMap::try_from_account_bytes(&after.data).unwrap(); - let entry = map.find_by_symbol("SOL").unwrap().unwrap(); - let price = entry.metadata.oracle_price(); - - assert_eq!(price.mark_price.price.ticks.as_inner(), before_mark); - assert!( - price - .mark_price - .spot_price_component - .last_exchange_spot_price - .iter() - .all(|value| value.slot == 100 && value.ticks.as_inner() == 8_000) - ); - assert!( - price - .mark_price - .perp_price_component - .last_exchange_perp_price - .iter() - .all(|value| value.slot == 100 && value.ticks.as_inner() == 7_000) - ); -} - -fn phoenix_trader_fixture(collateral: i64) -> Vec { - let header_len = core::mem::size_of::(); - let mut data = vec![0_u8; header_len + 16 + 80]; - data[..8].copy_from_slice(&PhoenixAccount::Trader.discriminant()); - data[88..96].copy_from_slice(&collateral.to_le_bytes()); - data[112..116].copy_from_slice(&2_u32.to_le_bytes()); - data[header_len..header_len + 8].copy_from_slice(&1_u64.to_le_bytes()); - data[header_len + 8..header_len + 16].copy_from_slice(&2_u64.to_le_bytes()); - data[header_len + 16..header_len + 24].copy_from_slice(&42_u64.to_le_bytes()); - data -} diff --git a/crates/mcp/src/surfpool/mod.rs b/crates/mcp/src/surfpool/mod.rs index 369f0909c..e42933f31 100644 --- a/crates/mcp/src/surfpool/mod.rs +++ b/crates/mcp/src/surfpool/mod.rs @@ -890,13 +890,12 @@ impl Surfpool { // Get the template for validation and normalization if let Some(template) = registry.get(&override_instance.template_id) { - if template.id == "phoenix-trader-collateral-stress" { - if let Err(error) = validate_collateral_fields(&override_instance.values) + if template.id == "phoenix-trader-collateral-stress" + && let Err(error) = validate_collateral_fields(&override_instance.values) .and_then(|_| validate_hot_trader_fields(&override_instance.values)) - { - validation_errors.push(error.to_string()); - continue; - } + { + validation_errors.push(error.to_string()); + continue; } // Normalize: Extract values from malformed PDA seeds // LLMs sometimes put values directly in seeds instead of in values map @@ -1142,6 +1141,33 @@ impl Surfpool { self.stage_scenario(scenario).await } + async fn list_phoenix_markets_from_surfnet( + &self, + surfnet_port: Option, + ) -> Result { + let globals = self + .fetch_surfnet_accounts(surfnet_port, &[PHOENIX_GLOBAL_CONFIG]) + .await?; + let global_account = globals[0] + .as_ref() + .ok_or("Phoenix GlobalConfig account was not found on the surfnet")?; + let perp_asset_map = + phoenix_perp_asset_map_address(global_account).map_err(|error| error.to_string())?; + let maps = self + .fetch_surfnet_accounts(surfnet_port, &[perp_asset_map]) + .await?; + let map_account = maps[0].as_ref().ok_or_else(|| { + format!("Phoenix PerpAssetMap account {perp_asset_map} was not found on the surfnet") + })?; + let symbols = phoenix_market_symbols(perp_asset_map, map_account) + .map_err(|error| error.to_string())?; + Ok(serde_json::json!({ + "perpAssetMap": perp_asset_map.to_string(), + "count": symbols.len(), + "symbols": symbols, + })) + } + #[tool( description = "Lists the Phoenix Eternal perp markets currently listed on the live PerpAssetMap. Reads the fork's GlobalConfig and the map it points at, so the catalog reflects live state rather than a hardcoded snapshot. Use it to discover valid market symbols before preparing a Phoenix scenario." )] @@ -1149,52 +1175,15 @@ impl Surfpool { &self, Parameters(params): Parameters, ) -> Result { - let accounts = match self - .fetch_surfnet_accounts(params.surfnet_port, &[PHOENIX_GLOBAL_CONFIG]) - .await - { - Ok(accounts) => accounts, - Err(error) => return Ok(scenario_tool_error(error)), - }; - let Some(global_account) = accounts[0].as_ref() else { - return Ok(scenario_tool_error( - "Phoenix GlobalConfig account was not found on the surfnet".to_string(), - )); - }; - - let perp_asset_map = match phoenix_perp_asset_map_address(global_account) { - Ok(address) => address, - Err(error) => return Ok(scenario_tool_error(error.to_string())), - }; - - let map_accounts = match self - .fetch_surfnet_accounts(params.surfnet_port, &[perp_asset_map]) + match self + .list_phoenix_markets_from_surfnet(params.surfnet_port) .await { - Ok(accounts) => accounts, - Err(error) => return Ok(scenario_tool_error(error)), - }; - let Some(map_account) = map_accounts[0].as_ref() else { - return Ok(scenario_tool_error(format!( - "Phoenix PerpAssetMap account {perp_asset_map} was not found on the surfnet" - ))); - }; - - let symbols = match phoenix_market_symbols(perp_asset_map, map_account) { - Ok(symbols) => symbols, - Err(error) => return Ok(scenario_tool_error(error.to_string())), - }; - - let payload = serde_json::json!({ - "perpAssetMap": perp_asset_map.to_string(), - "count": symbols.len(), - "symbols": symbols, - }); - let json = match serde_json::to_string(&payload) { - Ok(json) => json, - Err(error) => return Ok(scenario_tool_error(error.to_string())), - }; - Ok(CallToolResult::success(vec![Content::text(json)])) + Ok(payload) => Ok(CallToolResult::success(vec![Content::text( + payload.to_string(), + )])), + Err(error) => Ok(scenario_tool_error(error)), + } } #[tool( @@ -1543,23 +1532,6 @@ mod tests { ); } - #[test] - fn phoenix_collateral_requires_exact_string_inputs() { - assert!( - serde_json::from_value::(serde_json::json!({ - "trader": "GHkq1eHeZGi96RmGPZ23e3BDEcdfpPzsPwpYdm17SWgc", - })) - .is_err() - ); - assert!( - serde_json::from_value::(serde_json::json!({ - "trader": "GHkq1eHeZGi96RmGPZ23e3BDEcdfpPzsPwpYdm17SWgc", - "targetQuoteLots": 1, - })) - .is_err() - ); - } - #[test] fn phoenix_collateral_fetches_dependencies_only_for_hot_traders() { use surfpool_core::{ @@ -1643,57 +1615,26 @@ mod tests { } #[tokio::test] - async fn create_scenario_rejects_other_trader_state_fields_before_staging() { - let mut scenario = Scenario::new("flags".to_string(), "not mirrored".to_string()); - scenario.add_override( - surfpool_types::OverrideInstance::new( - "phoenix-trader-collateral-stress".to_string(), - 0, - surfpool_types::AccountAddress::Pubkey(Pubkey::new_unique().to_string()), - ) - .with_values(HashMap::from([( - "traderState.flags".to_string(), - serde_json::json!(0), - )])), - ); - let result = Surfpool::new() - .create_scenario(Parameters(scenario)) - .await - .unwrap(); - assert!(json_of(&result)["url"].is_null()); - assert!(json_of(&result).to_string().contains("traderState.flags")); - } - - #[tokio::test] - async fn create_scenario_rejects_legacy_phoenix_collateral_before_staging() { - for include_canonical in [false, true] { - let mut scenario = Scenario::new("old collateral".to_string(), "old input".to_string()); - let mut values = - HashMap::from([("quote_lot_collateral".to_string(), serde_json::json!("1"))]); - if include_canonical { - values.insert( - "traderState.quoteLotCollateral".to_string(), - serde_json::json!("2"), - ); - } + async fn create_scenario_rejects_unsupported_phoenix_collateral_fields_before_staging() { + for (field, expected) in [ + ("quote_lot_collateral", "use traderState.quoteLotCollateral"), + ("traderState.flags", "traderState.flags"), + ] { + let mut scenario = Scenario::new(field.to_string(), "unsupported".to_string()); scenario.add_override( surfpool_types::OverrideInstance::new( "phoenix-trader-collateral-stress".to_string(), 0, surfpool_types::AccountAddress::Pubkey(Pubkey::new_unique().to_string()), ) - .with_values(values), + .with_values(HashMap::from([(field.to_string(), serde_json::json!("1"))])), ); let result = Surfpool::new() .create_scenario(Parameters(scenario)) .await .unwrap(); - assert!(json_of(&result)["url"].is_null()); - assert!( - json_of(&result) - .to_string() - .contains("use traderState.quoteLotCollateral") - ); + assert!(json_of(&result)["url"].is_null(), "{field}"); + assert!(json_of(&result).to_string().contains(expected), "{field}"); } }