End-to-end production onboarding for integrators: mint USDC → deploy vault → deposit → place LIMIT/MARKET orders → split for SELL. Copy-paste TypeScript that takes your associatedWallet from zero to its first fill using a partner API key.
Use this file to discover all available pages before exploring further.
This page takes the private key of your key’s associatedWallet
from nothing to fully trading in five steps. Every code block is
real ethers v6 against the production environment — paste it in, swap your
API key + private key, run.
Don’t have an API key yet? Keys are issued by PredictStreet
ops — they are never self-service. Email your integration manager
(or partners@predictstreet.com)
with the four items in
API keys → Getting a key and you’ll
receive a production ps_live_… key plus the IP-allowlist + KYC
prerequisites for orders:write and vault:write scopes.
Your EOA needs two things before the deposit step:
USDC in the EOA balance. Source it via a bridge / off-chain ramp
per your partner runbook. The platform does not mint USDC — it is
a real on-chain asset.
ADI for gas. ~5 ADI covers vault deposit + a handful of trades.
The platform deploys your vault automatically the moment your
associated wallet clears KYC tier 1 — you do not call
VaultFactory.deployVault yourself, and you do not pay the
deploy gas. KYC clearance is a hard prerequisite for any
orders:write / vault:write key, so by the time you have a
working API key, your vault is either deployed or about to be.Poll GET /api/me/vault until deployed: true:
async function waitForVault(timeoutMs = 60_000) { const start = Date.now(); while (Date.now() - start < timeoutMs) { const r = await fetch(`${BASE}/api/me/vault`, { headers: auth }); const me = await r.json(); // deployStatus is one of: not_started | pending | building | // submitted | confirmed | skipped_existing | failed_permanent // See /concepts/vaults/auto-deploy#deploystatus-values for // semantics. `deployed: true` means VaultFactory.vaultOf(eoa) // returns a non-zero address on chain. if (me.deployed) return me.vaultAddress; await new Promise(r => setTimeout(r, 1500)); } throw new Error('vault not auto-deployed within 60s — check KYC status');}const vault = await waitForVault();console.log('your vault:', vault);
Save vault. It’s the address you’ll send as maker in every
signed order, and the verifyingContract in vault-side EIP-712
domains (splits / withdrawals).
Vault deploy isn’t currently surfaced on the typed WS channels —
poll GET /api/me/vault until deployed: true like the snippet
above. Auto-deploy normally completes within a few seconds.
// One-time MaxUint256 approval is the standard pattern — saves gas// on every future deposit. Tighten if your security model needs it.await (await usdc.approve(vault, MaxUint256, { gasLimit: 100_000n })).wait();const v = new Contract( vault, ['function depositERC20(address token, uint256 amount)'], eoa,);await (await v.depositERC20( USDC, parseUnits('500', 6), // 500 USDC into the vault { gasLimit: 600_000n },)).wait();
After ~1 block the platform reflects the deposit and your
/api/me/balances will show available = 500 USDC. The on-chain approve
step stays on your side regardless of auto-deploy — ERC-20 transferability
is owner-only by design. See Deposits for
the full lifecycle and Deposit limits for
the daily / weekly / monthly caps applied to your vault.
Per-EOA deposit limits are initialised by the platform automatically
alongside the vault deploy. By the time GET /api/me/vault returns
deployed: true, the limits are already registered and the deposit
call succeeds.
Every order is signed against the on-chain CTFExchangeOrder
struct (11 fields). See EIP-712 signing
for the full type table. Helper:
Binary vs neg-risk domain. The verifyingContract you sign
against depends on the market: 2-outcome binary markets settle on
CTFExchange, 3+-outcome (neg-risk) markets settle on
PredictStreetNegRiskCtfExchange. The market response carries
negRiskEligible: boolean — branch on it before signing or you’ll
get bad_signature rejections at order placement:
HTTP 201 does not mean “order accepted by the matcher.” The
endpoint returns 201 Created as soon as core-api validates the
request envelope and forwards it — even when the matcher later
rejects it. Always branch on the body’s status field, not
the HTTP code:
if (resp.status === 'REJECTED') { // resp.code is the reason — e.g. 'bad_signature', // 'market_not_open', 'insufficient_balance'. Fix and retry. console.error('rejected:', resp.code, resp.message);}
A SELL order must reference outcome ERC-1155 tokens that already sit
in your vault. You get them by depositing USDC and then calling
vault.splitPosition(...) with a backend co-signature. The backend
returns the co-sig from POST /api/vault/split-signature; you sign
the same struct yourself, then submit on-chain.
The platform supports the following combinations end-to-end:
type
timeInForce
Behaviour
limit (default)
gtc (default)
Rests in the book until filled, expired, or cancelled.
limit
ioc
Fill what’s available at price-or-better right now; cancel any unfilled remainder.
limit
fok
Either fill the entire quantity at price-or-better immediately, or cancel without any partial fill.
market
ioc
Take the best resting prices up to your price slippage cap; cancel any unfilled remainder.
market
fok
All-or-nothing market — fill the entire quantity within the slippage cap, or cancel.
market
gtc
Rejected with code: invalid_tif — MARKET orders cannot rest.
price is always required, even for MARKET — it acts as the
slippage cap. The matcher will only consume liquidity at price or
better.expiry (= EIP-712 expiration) defaults to 0 (no on-chain
expiry). A non-zero value is fine for off-chain matching but races
async on-chain settlement; if the deadline passes between match and
submit, the on-chain leg reverts with OrderExpired(). Recommend
keeping 0 unless you specifically need a hard TTL well above
worst-case settlement latency.