Skip to main content
When you request a vault.splitPosition or vault.mergePositions co-signature from the backend, the platform places an off-chain lock on your vault’s balance for the duration of the signed deadline:
  • Split — locks USDC (available -> locked) so it can’t be spent twice while the chain tx is in-flight.
  • Merge — locks per-outcome ERC-1155 shares so a SELL order can’t consume the same shares the merge plans to burn.
In the happy path your wallet submits the on-chain tx, the platform indexes the corresponding PositionSplit / PositionsMerged event, and the lock is drained (consumed by chain) with the collateral side credited symmetrically. You never see the lock.

When recovery is needed

Three failure shapes leave the lock stuck because the chain side never moved:
  1. You never broadcast — you got the backend signature back but never submitted the tx.
  2. The tx was dropped — broadcast but the mempool evicted it before mining (low gas, replacement, etc.).
  3. The tx reverted — landed in a block but failed the contract check; no PositionSplit / PositionsMerged event was emitted.
In any of these the co-signed operation hits the EXPIRED state by wall-clock once deadline elapses, but the balance lock remains until either:
  • The backend’s automatic sweeper job tops it up (default behaviour), or
  • You call the user-driven recovery endpoint described below (used when the sweeper is intentionally disabled in observation-only mode for a deploy window).

The recovery endpoint

POST /api/me/splits-merges/{opId}/unlock Supported opKind: SPLIT and MERGE. CONVERT / REDEEM follow in subsequent releases. The endpoint runs a chain of safety gates before releasing the lock so an operation that did succeed on-chain can never be unlocked again (which would double-credit the vault):
  1. Ownership — the authenticated wallet must own the op’s vault (403 vault_owner_mismatch).
  2. Op kind — SPLIT or MERGE only (400 op_kind_unsupported).
  3. Already terminal — an op that is already refunded, or already CONFIRMED on-chain, returns an idempotent 200 with refundAppliedNow: false so retries are safe.
  4. Age — now - deadline ≥ 90 s (425 op_too_recent). The 90 s buffer matches the auto-sweeper’s safety window so a tx mined just after the off-chain deadline can never be double-counted.
  5. Indexing caught up — refuses the call while the platform’s chain indexing is lagging (503 chain_watcher_lagging; retry later). Without this gate a confirmed split that is not indexed yet would look like one that never executed and could be credited twice.
  6. Not executed on-chain — if a matching PositionSplit / PositionsMerged event exists for the vault, condition and amount, the call returns 409 op_confirmed_on_chain with the event’s txHash and blockNumber in the error details: the operation succeeded, so there is nothing to unlock.
On success the op transitions to EXPIRED and its locked balance is released back to available. See the full request / response shape under Vault → Release an EXPIRED split / merge lock in the API Reference.