Two-tier pause (L0-15). mode 1 = halt-open: opens and non-reduce-only order placement are blocked (#4), but closes, liquidations, stops and cross deposit/withdraw still work — traders can always de-risk and exit. mode 2 = full-freeze: even those are blocked symmetrically (#90); only cancel_order works, and it auto-degrades to mode 1 after 72h. mode must be 1 or 2 (use unpause for 0).
fn pause(env: soroban_sdk::Env, mode: u32) -> Result<(), NoetherError>
Resume fully (mode 0).
fn unpause(env: soroban_sdk::Env) -> Result<(), NoetherError>
Swap the running WASM in place; all storage (positions, orders, cross balances) is preserved across the upgrade.
⚠️ When the new wasm's MarketConfig has MORE fields than the stored
one, every config-reading entry point traps until migrate_config
rewrites it (Soroban decodes structs by exact field set). Pause →
upgrade → migrate_config → unpause. See migrate_config.
fn upgrade(
env: soroban_sdk::Env,
new_wasm_hash: soroban_sdk::BytesN<32>,
) -> Result<(), NoetherError>
Force-realize a winning position at the oracle mark while ADL is active for its asset (L0-1). Permissionless — the on-chain gates (flag active + net winner) are the consensus; the keeper's ranking walk is advisory ordering. Pause-exempt (same class as liquidate). The trader is paid in full through the normal close settlement and optionally compensated from the buffer (adl_compensation_bps).
fn adl_close(
env: soroban_sdk::Env,
caller: soroban_sdk::Address,
position_id: u64,
) -> Result
Get a specific order by ID.
fn get_order(env: soroban_sdk::Env, order_id: u64) -> Option
Liquidate an underwater position. Callable by anyone (keeper). Keeper receives liquidation reward.
keeper - Address executing the liquidation (receives reward)position_id - ID of position to liquidateKeeper reward amount
fn liquidate(
env: soroban_sdk::Env,
keeper: soroban_sdk::Address,
position_id: u64,
) -> Result
Rotate the admin (R-11: the multisig migration path). Both current and new admin must sign — a one-sided rotation to a mistyped address would brick every admin power forever.
fn set_admin(
env: soroban_sdk::Env,
new_admin: soroban_sdk::Address,
) -> Result<(), NoetherError>
fn initialize(
env: soroban_sdk::Env,
admin: soroban_sdk::Address,
oracle_adapter: soroban_sdk::Address,
vault: soroban_sdk::Address,
usdc_token: soroban_sdk::Address,
config: MarketConfig,
) -> Result<(), NoetherError>
Cancel a pending order. For limit orders, refunds the locked collateral.
trader - Address of the trader (must own order)order_id - ID of the order to cancelfn cancel_order(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
order_id: u64,
) -> Result<(), NoetherError>
Get a position by ID.
fn get_position(env: soroban_sdk::Env, position_id: u64) -> Option
L1-18: wire the referral registry. Every fee-bearing open then try-invokes referral.record_trade for the discount/payout split; unset (or a broken registry) is a silent no-op — fail-open, trading never blocks on referrals.
fn set_referral(
env: soroban_sdk::Env,
referral: soroban_sdk::Address,
) -> Result<(), NoetherError>
Apply funding to all positions (can be called periodically). Funding balances long/short interest:
fn apply_funding(env: soroban_sdk::Env) -> Result<(), NoetherError>
Execute a triggered order (called by keeper). Checks if price condition is met and executes the order. Keeper receives a fee for successful execution.
keeper - Address of the keeper executing the orderorder_id - ID of the order to executeKeeper reward amount
fn execute_order(
env: soroban_sdk::Env,
keeper: soroban_sdk::Address,
order_id: u64,
) -> Result
Whether ADL is active for an asset.
fn is_adl_active(env: soroban_sdk::Env, asset: soroban_sdk::Symbol) -> bool
Open a new leveraged position.
trader - Address of the traderasset - Asset symbol (e.g., "XLM")collateral - USDC collateral amount (7 decimals)leverage - Leverage multiplier (1-10)direction - Long or ShortThe created Position
fn open_position(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
asset: soroban_sdk::Symbol,
collateral: i128,
leverage: u32,
direction: Direction,
acceptable_price: i128,
) -> Result
Route a share of trading fees to a treasury address (insurance / operations funding). bps is the protocol share in basis points, capped at 50%.
fn set_fee_split(
env: soroban_sdk::Env,
treasury: soroban_sdk::Address,
bps: u32,
) -> Result<(), NoetherError>
Set a stop-loss order on an existing position. Automatically closes the position when price reaches trigger to limit losses.
trader - Address of the trader (must own position)position_id - ID of the position to protecttrigger_price - Price at which to close (7 decimals)slippage_tolerance_bps - Max allowed slippage in basis pointsfn set_stop_loss(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
position_id: u64,
trigger_price: i128,
slippage_tolerance_bps: u32,
) -> Result
Add collateral to an isolated position (L0-6), moving its liquidation price further away. Risk-REDUCING — deliberately NOT pause-gated (exit-only-pause philosophy, L0-15). Cross uses deposit_cross_margin.
fn add_collateral(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
position_id: u64,
amount: i128,
) -> Result<(), NoetherError>
Close an existing position.
trader - Address of the trader (must own position)position_id - ID of position to closeFinal PnL amount (positive = profit, negative = loss)
fn close_position(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
position_id: u64,
acceptable_price: i128,
) -> Result
Per-market risk params for an asset (L0-12) — the single source of truth the keeper (simulate) and gateway (contractReader) read.
fn get_asset_risk(
env: soroban_sdk::Env,
asset: soroban_sdk::Symbol,
) -> Option
Overwrite the stored MarketConfig WITHOUT reading the old value.
A Soroban struct is stored as an exact field map, so after an
upgrade that adds config fields the old value no longer decodes:
get_config traps with UnexpectedSize and the market is bricked
until this runs. This entry point never reads the old config, so it
is callable in that state — the one-way door out of a config-shape
upgrade. Admin-only; validated like initialize.
fn migrate_config(
env: soroban_sdk::Env,
config: MarketConfig,
) -> Result<(), NoetherError>
Emergency admin tool (2026-08 funding-counterparty upgrade): reset the funding baseline of the given positions to the current per-asset index, voiding their pending funding. Refuses unless the market is in full-freeze (mode 2) so nothing settles mid-reseed. Unknown ids are skipped; returns the number of positions touched.
fn reseed_funding(
env: soroban_sdk::Env,
ids: soroban_sdk::Vec,
) -> Result
L1-24: halt/resume trading in ONE market (admin). While halted, risk- increasing ops on the asset revert #92; closing/liquidating/cancelling still works — one broken feed no longer forces a whole-venue pause.
fn set_asset_halt(
env: soroban_sdk::Env,
asset: soroban_sdk::Symbol,
halted: bool,
) -> Result<(), NoetherError>
Set per-market risk params (L0-12). Admin-only. The asset must be a listed pair and params must satisfy the MM=IM/2 ladder invariant. The first successful call stamps RiskEpochTs, activating the ladder: from then on risk-increasing ops on UNconfigured assets fail #88, while positions opened before the stamp keep the legacy MM.
fn set_asset_risk(
env: soroban_sdk::Env,
asset: soroban_sdk::Symbol,
params: AssetRiskParams,
) -> Result<(), NoetherError>
Permissionless NAV freshener: recompute one asset's unrealized trader PnL at the current oracle price and push it to the vault, so NOE pricing tracks the market between trades. Returns the pushed value.
fn sync_asset_pnl(
env: soroban_sdk::Env,
asset: soroban_sdk::Symbol,
) -> Result
Current pause state as (mode, since). mode reflects the 72h auto-degrade logically (read-only, no persist).
fn get_pause_state(env: soroban_sdk::Env) -> (u32, u64)
L1-24 view: whether one market is halted.
fn is_asset_halted(env: soroban_sdk::Env, asset: soroban_sdk::Symbol) -> bool
Migrate the single global funding index into per-asset indices (L0-13). Admin-only, run inside the upgrade pause window after migrate_config + set_asset_risk. Seeds every pair's index at the legacy global cumulative value so every open position's pending funding is preserved by delta-continuity — no per-position writes.
fn migrate_funding(env: soroban_sdk::Env) -> Result<(), NoetherError>
Set a take-profit order on an existing position. Automatically closes the position when price reaches trigger to lock in profits.
trader - Address of the trader (must own position)position_id - ID of the positiontrigger_price - Price at which to close (7 decimals)slippage_tolerance_bps - Max allowed slippage in basis pointsfn set_take_profit(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
position_id: u64,
trigger_price: i128,
slippage_tolerance_bps: u32,
limit_price: i128,
) -> Result
One-shot bootstrap of the live counters for a market upgraded in place: the counter keys do not exist in pre-upgrade storage, so decrements would saturate at zero and the counts would under-report forever. Admin derives the true open totals (a ledger-entry walk of Position/Order ids — the same walk the keeper runs) and writes them here, exactly once. The latch is a DEDICATED marker, NOT the counter key: closes and liquidations run even while paused and their saturating decrement CREATES the counter key at zero, which must not lock the admin out of seeding. Seeding overwrites whatever phantom value such an early transaction left behind. initialize() sets the marker, so a freshly deployed market can never be re-seeded.
fn seed_open_counts(
env: soroban_sdk::Env,
positions: u32,
orders: u32,
) -> Result<(), NoetherError>
Permissionless solvency check: flips the asset's ADL flag on when pool coverage (buffer + LP USDC) falls under adl_trigger_ratio_bps of the payable winner uPnL, and off again above the clear ratio (hysteresis). Returns the flag. Pause-exempt, lenient price read — it must work in exactly the stress states it exists for.
fn check_adl_trigger(
env: soroban_sdk::Env,
asset: soroban_sdk::Symbol,
) -> Result
L1-18: rolling 14-day traded volume for a trader — the referral registry's create_code gate cross-reads this (min_code_volume). Read-only local window rotation, nothing persisted.
fn get_trader_volume(env: soroban_sdk::Env, trader: soroban_sdk::Address) -> i128
Place a limit entry order. Collateral is locked immediately when the order is placed. Order executes when price reaches trigger_price (based on trigger_condition).
trader - Address of the traderasset - Asset symbol (e.g., "BTC", "ETH", "XLM")direction - Long or Shortcollateral - USDC collateral to lock (7 decimals)leverage - Leverage multiplier (1-10)trigger_price - Price at which to execute (7 decimals)trigger_above - true = execute when price >= trigger, false = when price <= triggerslippage_tolerance_bps - Max allowed slippage in basis points (e.g., 100 = 1%)The created Order Place a limit order.
time_in_force parameter)0 = GTC (Good Till Cancel) — stays open until triggered or cancelled1 = IOC (Immediate Or Cancel) — executes immediately if conditions met, else cancelled2 = Post Only — rejected if it would execute immediately (guarantees maker fee)time_in_force)If `time_in_
fn place_limit_order(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
asset: soroban_sdk::Symbol,
direction: Direction,
collateral: i128,
leverage: u32,
trigger_price: i128,
trigger_above: bool,
slippage_tolerance_bps: u32,
time_in_force: u32,
) -> Result
Remove collateral from an isolated position (L0-6). Risk-INCREASING — pause-gated, IM-floor gated, and gated on a STRICT (fresh) oracle read so a stale/deviant print can't authorize margin extraction.
fn remove_collateral(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
position_id: u64,
amount: i128,
) -> Result<(), NoetherError>
L1-30: proceeds a full isolated close/liquidation paid the trader, keyed by position_id (temporary storage; 0 if unknown/expired). A vault_factory owner reads this to reconcile a keeper-executed protective close.
fn get_closed_proceeds(env: soroban_sdk::Env, position_id: u64) -> i128
Mark-to-market equity of one position (L0-20), for vault_factory NAV: max(0, collateral + uPnL − pending funding) at the lenient close price. Fail-closed — a missing position (#20) or an oracle failure propagates so the factory reverts (ValuationUnavailable) rather than mispricing.
fn get_position_equity(
env: soroban_sdk::Env,
position_id: u64,
) -> Result
Trader's live fee-tier standing (C2 restore — the 128 KB WASM limit gives the view room again; the OrderPanel fee preview reads it directly instead of estimating from the gateway). Read-only: the rolling window rotates on a LOCAL copy, nothing is persisted.
fn get_trader_fee_info(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
) -> TraderFeeInfo
Open a cross-margin position. Single transaction: if pool balance is insufficient, automatically transfers the shortfall from trader's wallet. Works like Binance cross-margin: no separate deposit step needed.
fn open_position_cross(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
asset: soroban_sdk::Symbol,
collateral: i128,
leverage: u32,
direction: Direction,
acceptable_price: i128,
) -> Result
Place a trailing stop order attached to a position. Tracks peak price, triggers when price drops trailing_percent from peak.
fn place_trailing_stop(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
position_id: u64,
trailing_percent_bps: u32,
slippage_tolerance_bps: u32,
) -> Result
Close a cross-margin position. PnL returns to cross-margin pool, not trader wallet.
fn close_position_cross(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
position_id: u64,
acceptable_price: i128,
) -> Result
Deposit USDC into cross-margin pool. Funds are held by the Market contract and shared across all cross positions.
fn deposit_cross_margin(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
amount: i128,
) -> Result<(), NoetherError>
Update trailing stop peak price for a specific order. Called by keeper for each trailing stop order per cycle.
fn update_trailing_peak(
env: soroban_sdk::Env,
order_id: u64,
) -> Result
Withdraw USDC from cross-margin pool. Only withdrawable if free margin remains sufficient after withdrawal.
fn withdraw_cross_margin(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
amount: i128,
) -> Result<(), NoetherError>
Close part of an isolated position (L0-6). close_size is NOTIONAL USD (same unit as Position.size). Settles only the closed portion's pnl + funding, refunds its pro-rata collateral (minus the closed portion's loss), releases its OI reservation, and shrinks the position in place — the collateral/size ratio is preserved so the stored liquidation_price stays valid and entry_cumulative_funding is untouched (only the closed portion's funding settled). Returns pnl on the closed portion. A full-size close delegates to close_position.
fn close_position_partial(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
position_id: u64,
close_size: i128,
) -> Result
Place a stop-limit order.
Phase 1: waits for price to hit trigger_price (stop).
Phase 2: once triggered, acts as limit order at limit_price.
Place a stop-limit order with optional time-in-force and reduce-only flags.
See place_limit_order for time_in_force encoding details.
Note: IOC and PostOnly apply to the limit phase (phase 1), not the stop phase.
fn place_stop_limit_order(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
asset: soroban_sdk::Symbol,
direction: Direction,
collateral: i128,
leverage: u32,
trigger_price: i128,
limit_price: i128,
trigger_above: bool,
slippage_tolerance_bps: u32,
time_in_force: u32,
) -> Result
A trader's open position ids — the per-trader index the contract keeps anyway for cross-margin aggregation. This is the point-read replacement for the deleted global scan: a wallet lists ITS positions in one bounded call (MAX_OPEN_POSITIONS_PER_TRADER) instead of walking every id on the market, and it works against any market without a gateway in the middle.
fn get_trader_position_ids(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
) -> soroban_sdk::Vec
Liquidate a cross-margin account. Closes ALL cross positions. Called by keeper when account equity < maintenance margin.
fn liquidate_cross_account(
env: soroban_sdk::Env,
keeper: soroban_sdk::Address,
trader: soroban_sdk::Address,
) -> Result
Get cross-margin balance for a trader (pool balance only).
fn get_cross_margin_balance(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
) -> i128
Get cross-margin position IDs for a trader.
fn get_cross_margin_positions(
env: soroban_sdk::Env,
trader: soroban_sdk::Address,
) -> soroban_sdk::Vec