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.

ToStandard marketNegative-risk market
BuyUSDT allowance for CTFExchangeUSDT allowance for NegRiskCtfExchange
SellOutcome tokens for CTFExchangeOutcome tokens for NegRiskCtfExchange and NegRiskAdapter
Split positionsUSDT allowance for ConditionalTokensUSDT allowance for NegRiskAdapter
Merge or redeemNothingOutcome tokens for NegRiskAdapter
Convert positionsNot availableOutcome 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:

  • operation is one of TRADE, SPLIT, MERGE, REDEEM and CONVERT. CONVERT exists only for negative-risk markets, and the SDK throws InvalidApprovalOperationError for a standard market.
  • isNegRisk and isYieldBearing describe the market.
  • side is optional and narrows a TRADE to 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:

StatusMeaning
checkingReading on-chain whether the approval is already in place.
skippedAlready in place, so nothing was sent.
submittingThe SDK is sending the transaction and waiting for it to confirm.
confirmedThe transaction succeeded.
failedThe 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 codeMeaning
create_order_insufficient_collateral_allowanceThe 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_approvedYour outcome tokens aren't approved for the exchange, so you can't sell them.
create_order_neg_risk_adapter_not_approvedOn a negative-risk market, your outcome tokens aren't approved for the NegRiskAdapter.

See Errors for every error code.

On this page