Skip to main content
A vault is a per-user on-chain custody contract — an EIP-1167 minimal proxy of VaultImplementation, deployed by VaultFactory. Every trader who places signed orders on PredictStreet has exactly one vault, and that vault is the address that actually holds your USDC and outcome ERC-1155 tokens on chain.

Why vaults

The exchange does not custody funds for you. Three things only the vault model gives you:
  • You own the keys. VaultImplementation exposes initiateEmergencyWithdraw + emergencyWithdrawERC20/ERC1155 after a 7-day timelock — even if PredictStreet goes dark, you pull your funds out yourself.
  • Per-user isolation. A bug or compromise affecting one vault cannot drain another. There is no pooled treasury.
  • Dual-signature withdrawals. External transfers (withdrawERC20, withdrawERC1155) require both your EIP-712 signature and a backend co-signature, so a stolen API key alone cannot move funds out — neither replaces the vault owner’s private key that signs the EIP-712 authorisation.

How vaults connect to trading

When you sign a vault-backed order (signatureType = 1):
Order.maker  = your vault address    (VaultFactory.vaultOf(eoa))
Order.signer = your EOA              (the key that produced the EIP-712 signature)
On-chain, CTFExchange._verifyVault enforces vaultFactory.vaultOf(signer) == maker, so the only way the maker field matches is if you actually deployed and own that vault. This is also why SELL needs the outcome ERC-1155 to live in the vault (not the EOA): the platform looks up positions keyed by maker = vault. Splitting USDC into a YES + NO outcome set therefore goes through vault.splitPosition(...) rather than the EOA calling ConditionalTokens directly. See Vault contract reference for the on-chain function shapes.

Lifecycle, in five lines

vaultOf(eoa) == 0x0000000000000000000000000000000000000000   ── (1)
                                                                │ deployVault

vaultOf(eoa) == 0xYourVault   (clone exists, ready for funds)  ── (2)
                                                                │ approve  +  depositERC20  (deposit limits auto-init by backend)

vault.balanceOf(usdc) > 0     (USDC custodied — can BUY)       ── (3)
                                                                │ vault.splitPosition (USDC → YES + NO)

vault.balanceOf(yesToken) > 0  (outcome tokens — can SELL)     ── (4)
                                                                │ withdrawERC20 / withdrawERC1155 (dual-sig)

funds back to your EOA or external address                     ── (5)

Two ways to get a vault

The platform deploys every partner-managed vault automatically. The trigger differs by partner kind:
  • single_wallet and multi_wallet with requirePerWalletKyc=true — the back-office wallet calls VaultFactory.deployVault(eoa) the moment the EOA’s kyc_status flips to APPROVED (tier ≥ 1) and initialises deposit-limit caps in the same flow.
  • multi_wallet with requirePerWalletKyc=false (KYB-onboarded prime brokerage) — the deploy job is enqueued on the first authenticated request that names a previously-unseen sub-account in X-User-Wallet, with no retail KYC required for the sub-account.
Partners do not call VaultFactory.deployVault themselves and do not pay deploy gas. See Backend auto-deploy for the trigger detail, polling pattern, and the seven deployStatus values. VaultFactory.deployVault(eoa) is a public function callable by any address — operations can call it from a treasury wallet in a true emergency, the on-chain result is identical to auto-deploy. But this is exclusively a recovery path; partner integration code should never include it.