Instant Open hooks
React hooks over the core Instant Open flow. The three-function split (prepare / submit / auto) applies here too — pick based on how much control you want.
The page is split into two groups:
- Reads — pending queue reads (
useInstantOpens,useInstantOpenQuoteId). - Writes — the three-function open path (
useInstantOpenAuto,useInstantOpen), the LIMIT-order variant (useLimitOpenAuto), plus the composed orchestrator (useInstantOpenWithTpSl).
Import
import {
useInstantOpen,
useInstantOpenAuto,
useLimitOpenAuto,
useInstantOpenWithTpSl,
useInstantOpens,
useInstantOpenQuoteId,
} from "@symmio/trading-react";Reads
useInstantOpens
Paginated list of the solver’s pending instant-opens for a SubAccount. useManagedQuotes consumes this internally.
const pending = useInstantOpens({ account: subAccount, offset: 0n, size: 20n });useInstantOpenQuoteId
Resolve the on-chain quoteId for a given tempQuoteId. useManagedQuotes and the notification handlers use this to link rows.
const quoteId = useInstantOpenQuoteId({ tempQuoteId: -1001 });Writes
useInstantOpenAuto
The default — one-line orchestrator. Runs prepareInstantOpenParams inline (fetches market metadata, mark price, locked params, fee rates) then signs and POSTs.
const open = useInstantOpenAuto();
<Button
disabled={open.isPending}
onClick={() =>
open.mutate({
from: sessionKey,
subAccountAddress: subAccount,
market: { id: 1 },
positionType: PositionType.LONG,
initialMargin: "100",
leverage: 5,
slippage: 1,
})
}
>
{open.isPending ? "Opening…" : "Open"}
</Button>;Latency: prepare fetches run inline, so click → sign gap includes the network hop. Fine for most flows; see useInstantOpen below for the low-latency variant.
Full-balance variant (lowcap only) — replace initialMargin with fund: { mode: "full-balance", balance } to deploy the wallet’s entire selected collateral into the position’s VA; the SDK sizes the quantity so its locks + open-side fees + settlement fit inside the balance (close fees are charged at close from the position, not funded here). Funding is a one-of: pass exactly one of the two. See Full-balance funding for the sizing semantics.
open.mutate({
from: sessionKey,
subAccountAddress: subAccount,
market: { id: 1 },
positionType: PositionType.LONG,
fund: { mode: "full-balance", balance: "100" }, // deploy all $100 — no initialMargin
leverage: 5,
// slippage optional on lowcap — auto-derived from the dry-run estimate
});useLimitOpenAuto
The LIMIT-order variant — same one-line shape as useInstantOpenAuto, but the user’s resting price is the trigger and the order writes a pending on-chain quote instead of filling immediately. Majors / Rasa only; throws UNSUPPORTED_BY_SOLVER when the resolved solver has no limit support (gate the UI with useSupportsLimitOrder). Defaults the send deadline to 15 minutes. Funding is always a typed initialMargin here — the full-balance fund mode is lowcap-only, and limit orders are majors-only.
const place = useLimitOpenAuto();
place.mutate({
from: sessionKey,
subAccountAddress: subAccount,
market: { id: 1 },
positionType: PositionType.LONG,
initialMargin: "100",
leverage: 5,
price: "64000", // resting limit price
});On success it invalidates the hedger’s instant-open feed, so the order surfaces immediately as an offchain row in useLimitOrders; the anchor notification then flips it to onchain. Cancel it with useRequestToCancelQuote.
usePrepareInstantOpenParams
Live-preview the exact InstantOpenParameters a submit will sign — order (price + quantity), lockedParam, margin.amount, solverFeeCaps — by running prepareInstantOpenParams as a read. prepareInstantOpenParams neither signs nor submits, so this is safe to run on every input change; drive a “quote preview” from it and what the user sees is bit-for-bit what sendQuote receives — no separate local recomputation to drift, and the full-balance rescale + lot snap come for free.
Like useInstantOpenFees, it pre-fetches every input through its own cached queries (market metadata, mark price, locked params, on-chain fee rates, solver info, and the lowcap dry-run estimate) and passes them in as prefills, so the query function is pure. It also returns estimatedOpenPrice — the estimate the params were built with.
The returned estimatedOpenPrice is a decimal string when available, null when the estimate settled but was unavailable, and undefined while pending or on a non-lowcap solver. Forward it unchanged to submission, including null, so core does not fetch an unavailable estimate again. With automatic or explicit full-balance sizing, the estimate affects the final quantity and funding decision as well as margin.amount.
For automatic fallback, pass the raw available balance as availableBalance alongside initialMargin. The hook returns data.fundingMode ("initial-margin" or "full-balance") from core. Keep the typed input editable and show an adjustment message when the effective mode is "full-balance". Prefer this resolved funding check to comparing the typed amount against a fee-adjusted allowance. See automatic fallback for the exact rules.
A typed initialMargin above availableBalance produces a synchronous validationError, including while offline, before requesting a price estimate or other prefills. Display validationError ?? error: error and isError describe the calculation query, while validationError describes invalid input. Invalid balance funding disables dependent queries; correcting the input clears validationError and resumes preparation. Explicitly disabled hooks and empty inputs stay idle without validation. Automatic fallback applies only when the typed amount fits but its required funding exceeds the balance.
The hook also returns isReady: it is true only when required inputs, the debounced estimate, and the current preparation have settled successfully. Gate submission on isReady; previous data may remain visible while it is false. Pass the same availableBalance to the fee preview and useInstantOpenAuto / useInstantOpenWithTpSl submission. Take TP/SL quantity from the prepared order, including when fallback activates automatically.
const preview = usePrepareInstantOpenParams({
subAccountAddress,
from: sessionKey,
market: { id: symbolId },
positionType,
initialMargin,
availableBalance, // raw collateral decimal string; lowcap automatic fallback
leverage,
slippage,
});
// Display this error before rendering the preview.
const error = preview.validationError ?? preview.error;
// render preview.data — the exact params the send signs
// preview.data?.order.quantity, preview.data?.lockedParam.cva, preview.data?.margin.amount
const open = useInstantOpenWithTpSl();
if (preview.isReady) {
open.mutate({ ...variables, initialMargin, availableBalance, estimatedOpenPrice: preview.estimatedOpenPrice });
}useInstantOpen
Low-latency primitive — the caller supplies the fully-resolved InstantOpenParameters bag. Pair it with a usePrepareInstantOpenParams preview staged earlier (on trade-panel mount) so the critical path is just sign + POST.
const prepare = usePrepareInstantOpenParams(uiInputs);
const open = useInstantOpen();
// user clicks Open — no fetches on the critical path
if (prepare.isReady && prepare.data) open.mutate(prepare.data);useInstantOpenWithTpSl
Orchestrator that opens a position and immediately attaches TP/SL against the returned tempQuoteId. Two mutations chained under the hood:
instantOpenAuto→ returnstempQuoteId.setQuoteTpSlwithquoteId: tempQuoteId→ creates the conditional-order legs.
const open = useInstantOpenWithTpSl();
open.mutate({
...instantOpenInputs,
tpsl: hasTpOrSl
? {
from: sessionKey,
virtualAccount: predictedVa,
subAccount,
symbolId,
positionType,
quantity,
pricePrecision,
tp: tpInput,
sl: slInput,
}
: undefined,
});virtualAccount is the position’s VA, which does not exist on-chain yet — derive it with usePredictedNextVirtualAccount keyed by isolationTypeForSide(positionType) and symbolId. tpsl: undefined (both legs empty) collapses the call to a plain useInstantOpenAuto. The open and the TP/SL leg fail independently: a rejected TP/SL still resolves the mutation and surfaces data.tpslError rather than throwing away a landed open.
In full-balance mode (fund: { mode: "full-balance", balance } instead of initialMargin) the submitted quantity is the SDK-rescaled one — not what the trade form derived from a typed margin. Take the tpsl.quantity from usePrepareInstantOpenParams’s data.order.quantity (or useInstantOpenFees’s data.quantity): both run the same sizing as the open, so they equal what the open submits.
On success, useTpSlStore is populated with the confirming record keyed by tempQuoteId. When the position anchors on-chain, the solver maps tempQuoteId ↔ quoteId and the same TP/SL record is reachable by either id. Confirmation is WebSocket-driven — the record sits in confirming until the handler’s report frame flips it to new; read it with useQuoteTpSl({ quoteId, account }).
See TP/SL for the store + reconciliation model.
Related
- Core Instant Open — the three-function split explained.
- TP/SL —
useInstantOpenWithTpSlcomposes over these. - Unified Quotes —
useManagedQuotesreconciles the pending list with on-chain reads + notifications.