Summary
Bind mode is Symmio's oracle-less trading path. A partyA binds to a single solver, and that solver can open positions, fill
closes, and charge funding for the bound partyA without presenting Muon signatures. The deprecated settleUpnl
already participated in this model. When the target partyA was bound to the caller, the Muon settlement signature was not
verified.
Its replacement, settleUpnlUnified, did not: it required a valid Muon signature on every call, so a bound solver
that had migrated off the deprecated entry point was forced back onto the oracle for routine settlement. v0.8.6 closes that
gap. When the caller is settling its own book and every partyA in the payload is bound to it, both the Muon verification and
the upnl-based solvency checks are skipped.
When verification is skipped
All three conditions must hold; failing any one of them routes the call through full Muon verification:
-
The caller is
sig.partyB. The solver is settling its own book. A third-party partyB settling someone else's positions always needs a Muon signature, since it would otherwise control the upnl inputs to another solver's settlement. -
The caller is bindable.
isPartyBBindableis still set. Revoking bindability immediately revokes the oracle-less privilege protocol-wide, including here. -
Every partyA in
sig.partyAsis bound to the caller. The unified payload can settle several partyAs at once; one unbound partyA in the batch means the whole call is verified.
The binding comparison is the same one used by every other bound flow: bindState[partyA].partyB against the
caller. A partyA in the PENDING_UNBIND cooldown still counts as bound until the unbind completes, which is the
protocol-wide behavior. The cooldown prevents obligations from being dodged through an immediate unbind.
What is skipped and what still runs
| Check | Reads | Bound path |
|---|---|---|
| Muon TSS + gateway signature, timestamp freshness | payload | skipped |
PartyB solvency (upnlPartyB / upnlPartyBPerPartyA) |
payload | skipped |
PartyA solvency (upnlPartyAs) |
payload | skipped |
| PartyA not in liquidation, partyB not liquidating against it, not cross-liquidating | storage | enforced |
| Symbol not frozen for corporate-action adjustment | storage | enforced |
Quote ownership (quote.partyB == sig.partyB, partyA index mapping), open-position status |
storage | enforced |
| Updated price away from the opened price and no farther than the current price (opened exclusive, current inclusive) | both | enforced |
PartyB-actions pause, onlyPartyB, upnl nonce bumps for both parties |
storage | enforced |
Why the bound path skips solvency checks
Settlement realizes unrealized PnL: it rewrites a quote's openedPrice and transfers exactly the corresponding
difference between allocated balances. It is equity-neutral by construction, so whatever prices the payload claims, each
party's allocated balance moves by precisely the amount its unrealized PnL moves the other way. A bound solver feeding
fabricated prices cannot change either party's equity, and the balance mutations are underflow-guarded so no settlement can
push a balance negative.
Binding gives PartyA no control over the settled price. A bound solver already opens and closes that partyA's positions without an oracle, and realizing PnL on an open position applies the same authority applied to a position that already exists.
A regression test pins one second-order effect.
partyBAvailableForQuote takes max(-upnl, mm) on the negative-uPNL branch rather than stacking the
realized loss on top of the modelled one because the two represent the same risk. A consequence is that a solver settling away
from the market can draw down its own margin buffer once: it converts up to its maintenance margin into withdrawable
collateral and lands exactly on the liquidation boundary, not over it.
The draw is capped at min(settled amount, mm), one wei more is refused, it does not compound across further
settlements, and the solver stays able to pay its bound partyA in full. So the solver can choose to run itself at minimum
margin without a market move. It draws down its own buffer, and nothing about the counterparty's claim or the protocol's
solvency changes.
Flows that keep full verification
- Third-party settlement. A different partyB settles on behalf of a partyA it shares positions with. Full Muon verification plus the settlement cooldown, exactly as before.
-
settlePartyBUpnlForLiquidation. Liquidators call it during PartyA liquidation. Its implementation verifies the unified settlement signature unconditionally, regardless of caller or binding state. -
Force-close settlement. The force-close workflow verifies its own
MuonFunction.ForceClosesignature unconditionally. -
Deprecated
settleUpnl. Its behavior is frozen for backward compatibility. It keeps its existing Muon skip for bound partyAs and still runs its (payload-based) solvency checks.
Integration notes
-
Bound solvers can settle through
settleUpnlUnifiedwithout fetching a Muon signature, provided the payload covers only their own book and only bound partyAs. Populate the upnl fields with real values. They are ignored on-chain in this path, but honest values keep off-chain reconciliation meaningful. - A batch mixing bound and unbound partyAs is verified as a whole. Split payloads by binding status if you want the oracle-less path for the bound subset.
- Both parties' upnl nonces are bumped per settled partyA in the bound path too, so any in-flight Muon signatures minted against the old nonces are invalidated exactly as in verified settlement.
-
For anyone linking the library directly:
LibSettlement.settleUpnlUnifiedgained a fifth parameter,skipSolvencyChecks. The facet derives it from the binding state; external integrations should passfalseunless they replicate the same gating.