Skip to main content
Every user gets their own VaultImplementation clone (EIP-1167 minimal proxy) deployed by VaultFactory on first deposit.

VaultFactory

  • deployVault is idempotent — calling it for a wallet that already has a vault is a no-op that returns the existing clone address.
  • At creation the clone is initialised with vaultOwner = user and auto-approvals to CTFExchange, NegRiskAdapter, PredictStreetNegRiskCtfExchange, ConditionalTokens.
  • deployVault consumes ~4M gas for the very first clone (storage initialisation of the auto-approvals). Send a gasLimit of 5M to avoid out of gas reverts. Subsequent re-calls are no-ops and cheap.

Bootstrap: vault → deposit

Per-EOA deposit limits in DepositLimitRegistry are initialised by the platform automatically in response to the VaultCreated event — partners do not call initializeLimits themselves. If you fire depositERC20 in the same block as deployVault, the deposit can briefly revert with selector 0x87138d5c = NotInitialized(); wait one block and retry.

VaultImplementation entry points

All mutating calls require two EIP-712 signatures (vault owner + factory owner). msg.sender is unconstrained.

Splitting USDC into YES + NO inside the vault

SELL orders need the outcome ERC-1155 to live in the vault (the on-chain _verifyVault check looks up the position by maker = vault). The supported flow is:
  1. Backend co-signature. POST /api/vault/split-signature with { marketId, amount }. Body of response carries the dual-sig payload the vault expects:
  2. Owner signature. Sign the same SplitPosition typed-data with the vault EIP-712 domain (see below) using the EOA key.
  3. On-chain submit. Call vault.splitPosition(kind, collateral, conditionId, partition, amount, salt, deadline, ownerSig, backendSig).
After the tx confirms, the vault holds equal balances of YES and NO ERC-1155 for conditionId, and SELL orders on either outcome become possible.

Merging YES + NO back into USDC

mergePositions is the inverse of splitPosition: it burns equal units of every outcome ERC-1155 inside the vault and returns the matching USDC. Useful for closing a hedged position before resolution, freeing locked outcome tokens back into spendable USDC, or just exiting a market early without waiting for the oracle. The flow mirrors split:
  1. Backend co-signature. POST /api/vault/merge-signature with { marketId, amount }. Response shape is identical to split’s, only the field name changes (pendingMergeId instead of pendingSplitId):
  2. Owner signature. Sign the same MergePositions typed-data with the vault EIP-712 domain. The struct field shape is identical to SplitPosition (same 7 fields); only the EIP-712 primary type changes from SplitPositionMergePositions. Use type table below.
  3. On-chain submit. Call vault.mergePositions(kind, collateral, conditionId, partition, amount, salt, deadline, ownerSig, backendSig).
After the tx confirms, the vault burns amount of each outcome’s ERC-1155 and credits amount USDC; the platform reflects the balance change with ~1-block lag and your /api/me/balances shows available += amount. Both SplitPosition and MergePositions use the same EIP-712 type shape on chain:
Binary-market only in MVP (outcomes == 2, partition [1, 2]). Neg-risk N-outcome merges land on the NegRiskAdapter path later.

Emergency withdraw

Backend can abort:

Digest invalidation

Why EIP-1167 minimal proxy

  • Gas-cheap deployment (~45k gas vs ~2M for a full copy).
  • Non-upgradeable — implementation pinned, admin can’t hot-patch.
  • Bug mitigation requires new factory + user-driven emergency withdraw.

EIP-712 domain

Note verifyingContract is the vault address, not the factory.