VaultImplementation clone (EIP-1167
minimal proxy) deployed by VaultFactory on first deposit.
VaultFactory
deployVaultis 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 = userand auto-approvals toCTFExchange,NegRiskAdapter,PredictStreetNegRiskCtfExchange,ConditionalTokens. deployVaultconsumes ~4M gas for the very first clone (storage initialisation of the auto-approvals). Send agasLimitof 5M to avoidout of gasreverts. 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.
| Function | Purpose |
|---|---|
withdrawERC20(token, to, amount, salt, deadline, ownerSig, backendSig) | External withdrawal |
withdrawERC1155(token, to, id, amount, salt, deadline, ownerSig, backendSig) | Withdraw outcome tokens |
splitPosition(kind, collateral, conditionId, partition, amount, salt, deadline, ownerSig, backendSig) | Mint full outcome set inside the vault |
mergePositions(...) | Collapse outcome set → collateral |
convertPositions(marketId, indexSet, amount, salt, deadline, ownerSig, backendSig) | Neg-risk NO-basket conversion. The negRiskAdapter reference is held as a constructor-set immutable on the vault clone, so it is not part of the signed struct — only the seven typed fields above. |
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:
- Backend co-signature. POST
/api/vault/split-signaturewith{ marketId, amount }. Body of response carries the dual-sig payload the vault expects: - Owner signature. Sign the same
SplitPositiontyped-data with the vault EIP-712 domain (see below) using the EOA key. - On-chain submit. Call
vault.splitPosition(kind, collateral, conditionId, partition, amount, salt, deadline, ownerSig, backendSig).
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:
- Backend co-signature. POST
/api/vault/merge-signaturewith{ marketId, amount }. Response shape is identical to split’s, only the field name changes (pendingMergeIdinstead ofpendingSplitId): - Owner signature. Sign the same
MergePositionstyped-data with the vault EIP-712 domain. The struct field shape is identical toSplitPosition(same 7 fields); only the EIP-712 primary type changes fromSplitPosition→MergePositions. Use type table below. - On-chain submit. Call
vault.mergePositions(kind, collateral, conditionId, partition, amount, salt, deadline, ownerSig, backendSig).
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
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
verifyingContract is the vault address, not the factory.