Vault contract 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.
VaultexposesinitiateEmergencyWithdraw+emergencyWithdrawERC20/ERC1155after 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. USDC leaves the vault through
withdrawERC20, which requires 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. There is no ERC-1155 withdraw: outcome tokens leave the vault only by trading, by being merged or redeemed into USDC, or through the emergency withdraw.
How vaults connect to trading
Every order on PredictStreet is vault-backed. The struct you sign must always carry:CTFExchange._verifyVault enforces
vaultFactory.vaultOf(signer) == maker, so the only way the maker
field matches is if it is the vault VaultFactory deployed for your EOA.
The platform always verifies your signature against this shape, and
the exchange rejects any other signatureType. signatureType = 0
(EOA, maker = signer) is deprecated and never validates: an order
signed that way recovers a different address and is rejected with
bad_signature. The REST body maker may be your EOA or your vault
(any other address is rejected with maker_mismatch: HTTP 401 on
/api/orders/place, a per-entry rejection on /api/orders/place-batch),
but either way the struct you sign must use the vault as maker.
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
Two ways to get a vault
The platform deploys every vault automatically. The trigger differs by partner kind:- single_wallet and multi_wallet with
requirePerWalletKyc=true— the back-office wallet callsVaultFactory.deployVault(eoa)as soon as the wallet has a platform account: a deploy is queued on its first sign-in and again on KYC approval, and a catch-up job queues one every minute for any account still without a vault. Vault deploy does not wait for KYC; deposits do — deposit limits are set separately, see Limits initialisation. - 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 inX-User-Wallet, with no retail KYC required for the sub-account.
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.