Withdraw hooks
Withdraw request lifecycle — initiate, cancel, finalize, plus reads for the pending queue.
Reach for useWithdraw first: it is the high-level entry point that takes
just { account, amount, receiver } (amount in the collateral token’s decimals)
and does the rest — it reads the subaccount’s isolation, picks the right withdraw
path, builds the withdraw part, and scales the deallocate amount — so the UI never
branches on where the collateral sits or scales anything. useInitiateWithdraw and
the deallocate-and-initiate variant remain available for callers that already know
the isolation and want to drive one specific path, or need custom (multi-part /
cross-chain) withdraw parts.
Import
import {
// Reads
useLastWithdrawRequestId,
usePendingWithdrawRequests,
useWithdrawRequest,
useWithdrawableTime,
// Writes
useWithdraw, // high-level entry point
useInitiateWithdraw,
useSimulateInitiateWithdraw,
useRequestCancelWithdraw,
useSimulateRequestCancelWithdraw,
useFinalizeWithdrawRequest,
useSimulateFinalizeWithdrawRequest,
} from "@symmio/trading-react";useWithdraw — high-level withdraw
Pass the subaccount account to the hook, then mutate with a minimal input:
{ amount, receiver }, with amount in the collateral token’s decimals (e.g. 6
for USDC). You do not pass isolationType, build parts, or scale anything — the
hook reads the subaccount’s SubAccountIsolationType via useSubAccount (deduped by
react-query with any other useSubAccount on the same key, e.g. one driving a balance
display), builds the classic same-chain withdraw part,
scales the deallocate amount, and dispatches on the isolation, which determines
which balance the funds are in and therefore which underlying action runs:
isolationType | Where the funds sit | What runs |
|---|---|---|
CUSTOM (cross-margin) | allocated (margin) | deallocateAndInitiateWithdraw — deallocate and initiate in one atomic tx |
MARKET / MARKET_DIRECTION (VA) | available | initiateWithdraw — initiate only |
A CUSTOM subaccount holds its collateral in the allocated balance, so it must
first be deallocated into the available balance; that path batches both legs into a
single transaction. Account-layer balances are 1e18-scaled regardless of the
collateral token’s decimals, so the hook scales your collateral-decimals amount to
the 18-decimal amount the deallocate leg needs. The VA modes already hold their funds
in the available balance, so they only initiate the request.
import { useWithdraw } from "@symmio/trading-react";
const { mutate } = useWithdraw({ account });
// amount in the collateral token's decimals (6-dec USDC → 1 USDC):
mutate({ amount: 1_000000n, receiver });On success it invalidates the subaccount’s balance reads (balanceInfo,
balanceOf) plus its pending-requests and withdrawable-time reads. On the CUSTOM
path the SDK fetches a fresh Muon uPnL signature for the deallocate leg unless you
pass one as upnlSig. Backed by the core withdrawAuto
action; reach for useInitiateWithdraw / the deallocate-and-initiate variant when
you already know the isolation or need custom withdraw parts.
Lifecycle overview
- Initiate —
useInitiateWithdrawsubmits a withdraw request. Enters the delayed queue. - Wait —
useWithdrawableTimetells the UI when the request becomes claimable. - Finalize —
useFinalizeWithdrawRequestclaims the funds after the delay. - Cancel (optional) —
useRequestCancelWithdrawcancels a pending request.
Each write has a paired useSimulate* — see Simulate then write.
Reads
useLastWithdrawRequestId
Id of the most recent request for an account.
const lastId = useLastWithdrawRequestId({ account });usePendingWithdrawRequests
Every queued (not yet claimable) request for an account.
const pending = usePendingWithdrawRequests({ account });useWithdrawRequest
Details for one specific request.
const request = useWithdrawRequest({ account, requestId });useWithdrawableTime
Timestamp when a queued request becomes claimable. Bind to a countdown / claim button.
const { withdrawableAt, isReady } = useWithdrawableTime({ requestId });Writes
Every write on this page is relayable: pass gasless: true in the mutation
variables to sign it as an InstantLayer operation and let the relayer pay the gas. See Gasless
hooks. A relayed write still pays an operational fee from the Core balance — budget it first, as
Gasless withdrawals describes.
useInitiateWithdraw / useSimulateInitiateWithdraw
Submit a withdraw request directly, without the isolation dispatch. This is the
initiate-only leg — the right call for a MARKET / MARKET_DIRECTION (VA)
subaccount whose funds are already in the available balance, or when you drive the
deallocateAndInitiateWithdraw variant yourself for a CUSTOM subaccount. Prefer
useWithdraw when you don’t want to
branch on isolation.
const simulation = useSimulateInitiateWithdraw({ account, amount });
const mutation = useInitiateWithdraw();useRequestCancelWithdraw / useSimulateRequestCancelWithdraw
Cancel a pending request.
const mutation = useRequestCancelWithdraw();
mutation.mutate({ account, requestId });useFinalizeWithdrawRequest / useSimulateFinalizeWithdrawRequest
Claim funds after the delay expires.
const mutation = useFinalizeWithdrawRequest();
mutation.mutate({ account, requestId });Gasless withdrawals
Relaying a withdrawal removes the gas, not the cost. The GaslessLayer charges an operational fee after the operation executes, from the Core balance of the account that pays — for a plain withdrawal, the sub-account itself. The withdrawal and its fee draw on the same balance, so budget both, in Core units:
required Core balance = withdrawal amount in Core units + operational fee in Core unitsThe withdrawal amount is in the collateral token’s decimals, while the Core balance and the fee are 18-decimal. Convert the amount with collateralToCore18 before you compare. A MARKET / MARKET_DIRECTION sub-account withdraws from its available balance:
import { collateralToCore18 } from "@symmio/trading-core";
import { useAccountBalanceOf, useSymmioChainId, useSymmioConfig, useWithdraw } from "@symmio/trading-react";
const config = useSymmioConfig();
const chainId = useSymmioChainId();
const { collateralDecimals } = config.getChainConfig(chainId).addresses;
const balance18 = useAccountBalanceOf({ account }).data; // 18-decimal Core units
const required18 = collateralToCore18(amount, collateralDecimals) + fee18;
const affordable = balance18 !== undefined && balance18 >= required18;
const { mutate } = useWithdraw({ account });
mutate({ amount, receiver, gasless: true }); // amount stays in collateral decimalsA CUSTOM sub-account holds its funds in the allocated balance and deallocates the amount first, so budget the same total against its allocatedBalance from useAccountBalanceInfo. The fee is drawn from the free balance first, then from the allocated balance.
fee18 is the operation’s quoted fee — totalFee18 from useGaslessFeeQuote for the batch you relay. The quote is a preview, so keep a margin, and reserve the budgets of any other relayed operations still pending. Neither the quote nor the transparent mode’s fee pre-flight checks that the payer can afford the fee. A shortfall is refused before acceptance with SIMULATION_REVERTED and the Core revert OperationalFee: Insufficient balance, which approving more allowance does not fix. Read current fees rather than hard-coding them; they are deployment configuration.
A relayed withdrawal that succeeds is initiated, not received. A succeeded relay, and the mutation that
resolves with it, means the withdraw request exists on-chain and its amount has left the Core balance. The funds reach
the receiver only after the cooldown and a separate finalize. Show “withdrawal initiated” until then, and follow the
request with useWithdrawableTime and usePendingWithdrawRequests.
Finalizing and cancelling cost a fee too when relayed. Each is its own relayed operation, charged in Core units to the request’s sub-account unless the daily free quota covers it:
- A relayed finalize adds nothing to the Core balance, so the fee must still be there when it runs. Withdrawing the whole balance leaves nothing to pay it with. Keep the fee back, or finalize from the wallet (
gasless: false), which costs native gas but no operational fee. - A relayed cancel of a pending request refunds the amount before the fee is collected, so the refund can cover the fee. A cancel that only asks the request’s withdraw provider to cancel refunds nothing yet, so it needs the reserve as well.
Related
- SYMMIO Contract — the underlying
createClassicWithdrawPartprimitive. - InstantLayer — the delegated instant-withdraw variant.
- Errors — reverts surface as
SymmioRequestErrorkind: "revert".