GuidesTrading
Approvals
Let the exchange contracts move your USDT and outcome tokens, so your orders can settle.
Orders settle on-chain. When an order fills, an exchange contract moves USDT from the buyer and outcome tokens from the seller. So the wallet that holds the funds must approve those contracts first. You do this once for each contract, not once for each order. Without the approvals, the API rejects your orders.
What to approve
A market's isNegRisk and isYieldBearing fields, from GET /v1/markets/{id}, tell you which contracts it uses. USDT approvals are ERC-20 allowances (approve). Outcome token approvals are ERC-1155 operator approvals (setApprovalForAll) on the conditional tokens contract that holds the market's shares. Which contract holds your shares lists it for each market type.
| To | Standard market | Negative-risk market |
|---|---|---|
| Buy | USDT allowance for CTFExchange | USDT allowance for NegRiskCtfExchange |
| Sell | Outcome tokens for CTFExchange | Outcome tokens for NegRiskCtfExchange and NegRiskAdapter |
| Split positions | USDT allowance for ConditionalTokens | USDT allowance for NegRiskAdapter |
| Merge or redeem | Nothing | Outcome tokens for NegRiskAdapter |
| Convert positions | Not available | Outcome tokens for NegRiskAdapter |
Negative-risk markets need the NegRiskAdapter for selling, because the exchange moves your outcome tokens through it. The SDK's TRADE steps include this approval for both sides.
Yield-bearing markets follow the same table, with YieldBearingConditionalTokens in place of ConditionalTokens, YieldBearingNegRiskAdapter in place of NegRiskAdapter, and their own CTFExchange and NegRiskCtfExchange. A yield-bearing negative-risk market holds its outcome tokens on its own NegRisk ConditionalTokens contract, not on YieldBearingConditionalTokens. The addresses are on Deployed contracts.
Set every approval at once
setApprovals() sends each approval that the wallet is still missing for buying and selling on every market type. It skips the ones already in place, so you can call it safely at startup. Each missing approval is one transaction, paid in BNB. It doesn't set the USDT allowances for splitting positions. For those, use the SPLIT steps described below.
import { ChainId, OrderBuilder } from "@predictdotfun/sdk";
import { Wallet } from "ethers";
const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!);
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet);
// sends each missing trading approval for every market type, and skips the ones in place.
const result = await orderBuilder.setApprovals();
if (!result.success) throw new Error("Failed to set approvals");Set only what an operation needs
To show users an approval checklist, ask the SDK for the steps one operation needs. Describe the operation with a scope:
operationis one ofTRADE,SPLIT,MERGE,REDEEMandCONVERT.CONVERTexists only for negative-risk markets, and the SDK throwsInvalidApprovalOperationErrorfor a standard market.isNegRiskandisYieldBearingdescribe the market.sideis optional and narrows aTRADEto buying (Side.BUY) or selling (Side.SELL). Without it, the steps cover both.
getApprovalSteps returns the steps in order. It reads nothing from the chain, so it works before a wallet is connected. An operation that needs no approval returns an empty list. For onboarding, getAllApprovalSteps() returns every step the protocol could need.
Then check which steps are already done with checkApprovals, and send the rest with runApprovals. In TypeScript, checkApprovals batches its reads into one call. In Python, it reads the steps one at a time.
import { ChainId, OrderBuilder } from "@predictdotfun/sdk";
import { Wallet } from "ethers";
const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!);
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet);
// isNegRisk and isYieldBearing come from the market, for example GET /v1/markets/{id}.
const steps = orderBuilder.getApprovalSteps({
operation: "TRADE",
isNegRisk: true,
isYieldBearing: false,
});
// mark the steps already in place, in one batched read.
for (const { step, satisfied } of await orderBuilder.checkApprovals(steps)) {
console.log(step.label, satisfied ? "done" : "to do");
}
// send the missing ones, one transaction each.
const report = await orderBuilder.runApprovals(steps, {
onProgress: ({ step, status }) => console.log(step.label, status),
});
if (!report.success) throw new Error("Failed to set approvals");Each step is plain data. Its id is stable, so you can key your own labels or translations off it. The SDK's label and description match the web app's wording.
{
id: "ERC1155_APPROVAL:CTF_EXCHANGE",
type: "ERC1155_APPROVAL", // or "ERC20_ALLOWANCE"
spender: "0x8BC0...B689", // the contract that gets the permission
token: "0x22DA...d244", // the conditional tokens contract, or USDT
label: "Approve Exchange",
description: "Allows you to interact with the exchange.",
}runApprovals removes duplicate steps, so you can pass the steps of several operations together. It checks each step first and skips it if it's already in place (skipSatisfied, on by default). It stops at the first failure (stopOnError, on by default). Its progress callback reports each step's status:
| Status | Meaning |
|---|---|
checking | Reading on-chain whether the approval is already in place. |
skipped | Already in place, so nothing was sent. |
submitting | The SDK is sending the transaction and waiting for it to confirm. |
confirmed | The transaction succeeded. |
failed | The transaction reverted or failed. |
To send a single step yourself, for example after the user confirms it, call setApproval(step). ERC-20 steps approve the maximum amount by default.
Predict accounts
The approvals belong to the Predict account, because it holds the funds. When you create the order builder with a Predict account, the SDK sends each approval through the account. Your Privy wallet signs those transactions and pays their gas, so it needs some BNB. See Authentication for how to set up the builder.
When an approval is missing
The API checks approvals and balances when you place an order. If that check doesn't finish before the API responds, the order is accepted and then cancelled when the check fails (see Place orders). Otherwise you get one of these errors when something is missing:
| Error code | Meaning |
|---|---|
create_order_insufficient_collateral_allowance | The USDT allowance for the exchange is lower than this order plus the unfilled part of your other open buy orders for the same outcome. |
create_order_exchange_not_approved | Your outcome tokens aren't approved for the exchange, so you can't sell them. |
create_order_neg_risk_adapter_not_approved | On a negative-risk market, your outcome tokens aren't approved for the NegRiskAdapter. |
See Errors for every error code.