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? 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 testnet
ps_live_… key plus the IP-allowlist + KYC
prerequisites for orders:write and vault:write scopes.Prerequisites
| What | Where |
|---|---|
Node ≥ 20 with ethers@6 installed | npm i ethers |
| PredictStreet API key | Issued by ops — see API keys. Format: ps_live_<keyId>_<secret> |
Private key of the key’s associatedWallet | Stored in your secrets manager. The wallet must have completed KYC tier 1 before write-scope keys are issued. |
| Testnet RPC | https://rpc.ab.testnet.adifoundation.ai/ |
| API base URL | https://core.api.dev.predictstreet.sde.adifoundation.ai |
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.
2. Get your vault address
The platform deploys your vault automatically the moment your associated wallet clears KYC tier 1 — you do not callVaultFactory.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:
WebSocket alternative. Subscribe to
user_activity on
/ws/user and watch for vault.deploy_confirmed instead of
polling — see
WebSocket — User events. The
poll above is shown for the quickstart because it’s
one-file copy-pasteable.3. Approve + deposit USDC into the vault
/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.4. Place orders
EIP-712 helper
Every order is signed against the on-chainCTFExchange Order
struct (11 fields). See EIP-712 signing
for the full type table. Helper:
Pull a real tokenId
LIMIT BUY (rests in the book)
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 callingvault.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.
side: 'sell':
Order types — what you can ask for
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.
Verifying state after each step
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
GET /api/me/vault keeps returning deployed: false past the typical 15-second window | KYC not yet approved (single_wallet) or sub-account not yet onboarded (multi_wallet) | See Backend auto-deploy → What if auto-deploy doesn’t fire |
depositERC20 reverts with selector 0x87138d5c | NotInitialized() — your code raced ahead of the auto-deploy job (deploy-tx mined but the cap-init tx hasn’t yet) | Wait one block and retry; or rely on GET /api/me/vault returning deployed: true before depositing |
400 bad_request on /api/orders/place with no detail | An unknown field in the body — forbidNonWhitelisted | Only send the whitelisted set above (no extra fields) |
400 bad_signature on order placement | maker ≠ VaultFactory.vaultOf(signer) on chain | Send your vault address, not the EOA |
code: invalid_tif | type: market with timeInForce: gtc | Use ioc or fok for MARKET |
code: insufficient_position on SELL | The outcome token isn’t in your vault | Run step 6 (split) first |
code: insufficient_funds on BUY | Not enough USDC available in vault | Top up via step 3 |
code: market_not_open | Market is PROPOSED / PAUSED / RESOLVED | Pick a market with status: OPEN |
MatchFailed(reason: 0xc56873ba) on settlement | OrderExpired() — your expiration slipped between match and on-chain submit | Sign with expiration: 0 |
Where to go next
EIP-712 signing reference
Full struct + Python / Go / Rust 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.