Skip to main content
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 live testnet — paste it in, swap your API key + private key, run.
Don’t have an API key yet? Partner keys are issued by PredictStreet ops — they are never self-service. Email your integration manager (or partners@predictstreet.com) with the items listed in API keys → Getting a key and you’ll receive a testnet ps_live_… key plus the KYC prerequisites for orders:write and vault:write scopes.

Prerequisites

1. Mint testnet USDC to your EOA

MockCollateral exposes a public mint(address, uint256) on testnet — mint as much as you want. Note: USDC needs to land in your EOA first; the vault deposit moves it from EOA → vault in a later step.
You also need a small ADI balance for gas. On testnet grab it from the public faucet — ~5 ADI to your EOA covers the mint, approve, deposit and split transactions. You never pay gas for the vault deploy or for trade settlement — the platform submits those.

2. Get your vault address

The platform deploys your vault automatically once your associated wallet has a platform account (its first sign-in to the web app) — you do not call VaultFactory.deployVault yourself, and you do not pay the deploy gas. An orders:write / vault:write key is issued only for a wallet that has signed in and cleared KYC tier 1, 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:
Multi-wallet partner keys. If your key acts for many wallets, the first authenticated request with a new X-User-Wallet (for example GET /api/me/balances or the GET /api/me/vault poll above) onboards that wallet: the platform deploys its vault and opens its deposit limits, typically within 30 seconds, with no per-wallet KYC. Public endpoints such as GET /api/events do not trigger it. Do not sign in to the web app with a wallet you intend to use through the partner key: that makes it a regular retail account (KYC tier 0), and deposits into its vault revert with KycRequired().
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).
Instead of polling, you can listen for vault_created on the user_activity channel of /ws/user. Auto-deploy normally completes within a few seconds.

3. Approve + deposit USDC into the vault

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 per-deposit, 30-day and 12-month caps applied to your wallet.
The platform sets your deposit tier and caps from your KYC level, independently of the vault deploy: deployed: true on GET /api/me/vault does not mean you can deposit yet. Before the first deposit, poll GET /api/me/deposit-limits until tier is non-zero and restrictions.depositBlocked is false. Stop polling if tier reads 5: the wallet has been rejected by compliance and every deposit reverts with UserIsRejected(). If tier is 1–4 but depositBlocked stays true, the cause is a deposit restriction or a fully used cap, not timing — see Deposit limits.

4. Place orders

EIP-712 helper

Every order is signed against the on-chain CTFExchange Order 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: binary markets settle on CTFExchange, neg-risk markets (groups of 2 or more outcomes) settle on NegRiskCtfExchange. Don’t infer the type from the outcome count: the market response carries negRiskEligible: boolean — branch on it before signing or you’ll get bad_signature rejections at order placement:
The Order struct itself is identical between the two — only the domain verifyingContract differs.

Pull a real tokenId (and the per-market fee)

LIMIT BUY (rests in the book)

HTTP 201 does not mean “order accepted.” The endpoint answers 201 Created both when the order is accepted and when the platform rejects it on a business check (signature, funds, market state, matching); the verdict is in the same response body. Always branch on the body’s status field, not the HTTP code:

MARKET BUY (best-effort, IOC)

MARKET requires timeInForce: ioc or fok. The price field still travels (it acts as a slippage cap — you only fill at price-or-better).

Cancelling

5. Split USDC → YES + NO so you can SELL

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.
After this confirms the vault holds 50 YES + 50 NO ERC-1155. Now SELL of either side works exactly like BUY but with side: 'sell':

Order types — what you can ask for

The platform supports the following combinations end-to-end: 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.

Verifying state after each step

Common pitfalls

Where to go next

EIP-712 signing reference

Full struct + TypeScript / Python signing examples.

Order lifecycle

PENDING → OPEN → PARTIAL → FILLED state machine + on-chain settlement.

Vault contract reference

Full ABI, dual-sig flow, emergency withdraw, digest invalidation.

Time-in-force semantics

GTC / IOC / FOK behaviour in detail.