Skip to main content

Perpetual margin & risk actions

Actions on POST /exchange. The request envelope, the EIP-712 signing rules, the number planes and the response shape are on that page and apply to every action here.

Leverage, isolated-margin and portfolio-margin controls for perpetual positions, plus the BOLE liquidation backstop pool. See margin modes and portfolio margin for the models.

Set leverage and margin mode

Set per-asset leverage and, optionally, flip the asset to isolated mode. Sender-authorized by default; an approved agent may set it as an owner it acts for.

{
"type": "update_leverage",
"params": { "asset": 2, "leverage": 25, "is_isolated": true }
}
FieldTypeRangeDescription
ownerhex address | omitted40 hex charsOptional: set as this account (approved agents only). Not digest-bound — resolved at admission
assetuint32Target asset
leverageuint32[1, 100] and ≤ per-asset dynamic capNew leverage
is_isolatedbooltrue also flips the asset to isolated mode

There is no separate margin-mode action: isolation is the is_isolated flag here.

asset must name a listed perp market. An id no DEX hosts is refused with PRECONDITION_FAILED and the message no perp market for asset. Not live yet: the check ships with the next node release. A live node writes a permanent leverage row for a market that does not exist.


Adjust isolated margin by a delta

Apply a signed margin delta to an isolated position (+ adds, withdraws). Sender-authorized by default; an approved agent may adjust it as an owner it acts for.

{
"type": "update_isolated_margin",
"params": { "asset": 1, "delta": "-12.5" }
}
FieldTypeDescription
ownerhex address | omittedOptional: adjust as this account (approved agents only). Not digest-bound — resolved at admission
assetuint32Target asset
deltadecimal (string or number)Signed margin delta; non-zero

Add margin to an isolated position

Add margin to an isolated position. Add direction only (positive amount). Sender-authorized by default; an approved agent may add margin as an owner it acts for.

info

Correction: this action is not strict-isolated only. This page said it was, and the chain has always accepted a PLAIN isolated position as well. The code is right and the earlier rule was wrong; nothing changed on the chain.

The action requires an open isolated position on asset — margin mode isolated or strict-isolated. A cross position is refused with PRECONDITION_FAILED and the message no isolated position. On a strict-isolated position this is the only margin action available, because update_isolated_margin refuses a withdrawal there. On a plain isolated position it is the add-only half of that action.

{
"type": "top_up_isolated_only_margin",
"params": { "asset": 5, "amount": "3.0" }
}
FieldTypeDescription
ownerhex address | omittedOptional: add margin as this account (approved agents only). Not digest-bound — resolved at admission
assetuint32Target asset
amountdecimal (string or number)Positive amount to add

Enroll or unenroll portfolio margin

Enroll or unenroll the account in portfolio margin.

{
"type": "user_portfolio_margin",
"params": { "enroll": true }
}
FieldTypeDescription
enrollbooltrue = enroll, false = unenroll

Enrollment is refused in two cases:

  • Account equity is below pm_min_equity (governance parameter, default 100 000 USDC).
  • The enrolled-account count is at the governed cap pm_max_enrolled_users (default 512). The chain re-prices EVERY enrolled account each block, so the count is a per-block cost, not just a per-account one. An account that is already enrolled is exempt: it can always re-enroll, and it can always unenroll. Unenrollment frees a slot.

The equity check runs first, so an underfunded account at the cap reads the equity refusal. See portfolio margin.


Unenroll from portfolio margin — alias

An alias for user_portfolio_margin with enroll: false. It carries no params.

It is always allowed. Unlike enrollment, unenrolling checks nothing: not equity, not the enrolled-account cap. Unenrolling an account that was never enrolled is a no-op, not an error, so a client may send it without first reading the account's state. Unenrolling frees a slot under the cap.

There is no pm_enroll tag, and no PmUnenroll signing type. To enroll, post user_portfolio_margin with enroll: true. To sign this alias, sign the canonical MetaFluxTransaction:UserPortfolioMargin type with enroll set to false — the two spellings share one digest.

{ "type": "pm_unenroll", "params": {} }

The action takes no fields. params may be an empty object or omitted.


Lend to, or draw from, the BOLE pool

The BOLE pool is the liquidation backstop. Any account may supply USD to it and take that supply back. Only an approved liquidator may draw from it. That asymmetry is the access model: three of the four kinds are open, and one is not.

There is no asset field. The pool holds one asset, so kind and amount are the entire body.

Its EIP-712 typed-data primary type is MetaFluxTransaction:BorrowLend. kind signs as a uint8, not as the string you post: Lend UnLend Borrow Repay sign as 0 1 2 3, in that order. Sign the number, post the string — see typed-data signing.

{
"type": "borrow_lend",
"params": { "kind": "Lend", "amount": "500" }
}
FieldTypeRange / valuesDescription
kindenum string"Lend", "UnLend", "Borrow", "Repay"Case-exact. Any other value fails decode with unknown variant, and the error lists the four
amountdecimal string> 0Amount in USD, as a JSON string

What each kind does, and what it refuses.

The callResult
"Lend"Supplies amount to the pool
"UnLend"Takes supply back. Rejected above what you have lent — insufficient lent balance
"Borrow"Draws from the pool. Rejected, AUTH_UNAUTHORIZED, unless the sender is an approved liquidator. This is the only kind with an allowlist
"Repay"Repays a draw. Rejected above what you owe — repay exceeds outstanding borrow
Any kind, amount at or below zeroRejectedamount must be positive
Any kind whose arithmetic would overflowRejected — code INTERNAL, message internal error. The node's own reason names the overflow, and the envelope replaces it: an overflow is our defect, not your input, so the text never reaches you. Do not grep for it. The pool never saturates, because a saturated total would silently break the supplied-versus-shares identity

The BOLE bound is active on this network. An amount above 1000000000000 is refused with amount exceeds bole bound. Probe one call on your target network if you send figures near that size.

The success message still reads borrowLend accepted (stub). The word is stale: the pool accounting is real and the balances move. Do not treat the message as a sign that nothing committed.