Trading

Limit Orders

Place, poll, and cancel one venue's limit order. Self-custody cancel signatures are on the signing page.

Start from Choose your integration. A limit order rests on one venue at a price you set. It does not smart-route. Statuses, including which ones are final, are on Order lifecycle.

Authenticate with x-app-id and Authorization: Bearer. Buys need spendable managed cash. Like a market fill, a managed buy can use cash on any supported chain: sufwan-local-test bridges it to the venue's chain after the order is accepted. Sells need shares of that outcome. sufwan-local-test reserves that amount while the order is open.

A new user has no managed wallet until you call GET /execution/deposit-addresses. Until it returns ready: true, a managed limit order is refused with 400 quote_unfillable (Wallet is not ready for limit orders).

limitPriceRaw and sizeRaw are 6-decimal integer strings. 530000 is a price of 0.53, scaled by 1e6. 1000000 is 1 share. The price must be greater than 0 and less than 1000000 (1000000 would be 1.00 and is rejected). GTD also requires a future expiresAt. clientOrderId is optional and unique per user. Reusing it returns 409. A refused request (any 4xx) does not use it up. An accepted order keeps it for good, even if the venue later fails it. Always send one, so a timeout can be resolved with GET /execution/orders?clientOrderId=. See Errors.

Check the market before you send. A limit order is not priced against a quote. GET /venue-markets has tickSize for every venue and minOrderSize only where the venue publishes a floor. A null minOrderSize means sufwan-local-test has no value for that market. It does not mean there is no minimum. Kalshi's notional floor is one of those gaps. Prefer the live book tick from the orderbook, and fall back to tickSize when no book is loaded. A size or tick refusal by sufwan-local-test is 400 with only a message, no code, and creates no order. A floor enforced at the venue is different: the order is accepted with 200 and then ends failed.

VenueSlugWhat sufwan-local-test checks
PolymarketpolymarketsizeRaw must be at least the market's minOrderSize when that field is set
PREDpredAt least 0.001 shares, aligned to 0.001. A buy must be at least $0.90 notional
HyperliquidhyperliquidWhole contracts, and a 0.00001 price tick. A fractional size is refused, not rounded. postOnly: true is sent as Alo, including when timeInForce is GTC
TipRuntiprunAt least 0.01 shares and a 0.01 price tick. Managed custody only
Predict.funpredictPrice tick from the market's tickSize. No minimum at placement: under 0.01 shares, or a buy under the venue's dollar minimum, is accepted and then ends failed
MyriadmyriadA 0.01 price tick. No minimum at placement: a venue floor ends the order failed
LimitlesslimitlessPrice tick only when the market has a tickSize. No minimum at placement: under 0.01 shares ends the order failed

Place a managed limit order

SDK
import { Venue } from "@agg-build/sdk";

await client.placeLimitOrder({
  venue: Venue.polymarket,
  venueMarketOutcomeId, // id from venueMarketOutcomes[].id
  side: "buy", // "sell" uses the same fields, not a different amount
  limitPriceRaw: "530000", // 530000 = 0.53, scaled by 1e6
  sizeRaw: "1000000", // 1000000 = 1 share
  timeInForce: "GTC",
});

Place limit order. The usual first status is pending: accepted, not yet on the book.

sufwan-local-test enforces the combinations below. This is not every value of the Venue enum.

VenueSlugtimeInForcepostOnly
PolymarketpolymarketGTC, GTD, FOK, FAKyes
LimitlesslimitlessGTC, FOKyes
Predict.funpredictGTC, GTDno
HyperliquidhyperliquidGTC, IOC, ALOyes
MyriadmyriadGTC, GTD, FOK, FAKno
PREDpredGTC, GTDno
KalshikalshiGTC, GTD, FOK, IOCyes
BetDEXbetdexGTC, IOCno
ProphetXprophetxGTC, FOKyes
NovignovigGTC, FOKyes
TipRuntiprunGTC, GTD, FOK, IOCGTC and GTD only

TipRun takes buy and sell. A GTD expiresAt must be 10 minutes to 30 days out. A GTC order rests for at most 22 days, then ends as venue-expired. TipRun nets opposite directions on one contract, so an order opposite to one of the user's open TipRun orders on that contract (for example buy No while buy Yes rests) is refused with 400. Maker fills cost 0% and taker fills 1%.

ProphetX and Novig accept that pair only in Direct API mode, and only on a buy. Both reject expiresAt. BetDEX uses hosted venue cash, not a signing wallet: GTC or IOC, no post-only, and no expiry. Odds are rounded conservatively and stake is rounded down. Unmatched GTC size is cancelled when the market goes in-play. A sell closes the same outcome and needs collateral as well as the shares. Execution can be unavailable. An unsupported venue, time in force, or post-only flag is rejected before the order reaches the venue.

A limit order gets the same region check as a fill, for the one venue it names. A user who cannot trade that venue from their region gets 403 geo_blocked, and no order is created. A limit order has no quote, so to know beforehand, get a buy quote for that outcome and read its warnings. See Venue geo availability.

Self-custody placement is the same call with signingAddress set to a linked EOA, and only when that is enabled for your app. Polymarket allows GTC or GTD, buy or sell, at or above the market minimum, and the deposit wallet must already exist. The deposit wallet places the order, unless the user has a legacy Polymarket Safe whose free pUSD (buy) or free shares (sell) cover the whole order; then the Safe places it and nothing moves. Otherwise, when enabled for your app, the Safe can move pUSD or shares into the deposit wallet first, which adds one Safe signature. A legacy proxy wallet is not supported. Hyperliquid allows GTC or ALO, buy or sell, whole contracts, and a 0.00001 price tick, with no expiresAt. Self-custody IOC is rejected. expiresAt is accepted only with GTD.

fundingAddresses is buy-only, requires signingAddress, and can only fund by bridging in. On the venue's own chain, only the signing wallet pays. Omit approveMode, or send approveMode: "sponsored", for the same EIP-7702 approval as a market fill. approveMode: "user_broadcast" makes the wallet send the approve. See Choosing an approve mode.

The response includes quoteId for a funded buy. Poll getExecutionStatus({ quoteId }) and submit signatures on Self-custody signing. Do not call fillSelfCustody for that quote. Placement has already started it. An admitted pending order has not reached the venue. A self-custody buy does not reserve a managed balance.

Poll a managed limit order

open is not the only resting status, and filled is not the only finished one. partially_filled_open is resting and not terminal: some size filled, and the rest is still on the book. partial_fill is terminal: the unfilled rest will not fill. cancel_pending is not done. The loop returns on the resting pair and on a terminal status, so it does not spin on those two.

const resting = new Set(["open", "partially_filled_open"]);
const terminal = new Set(["filled", "partial_fill", "cancelled", "expired", "failed"]);

async function waitForLimitOrder(orderId: string) {
  for (;;) {
    const page = await client.getExecutionOrders({ orderId, limit: 1 });
    const current = page.data[0];
    if (!current) throw new Error("Order was not found");
    if (resting.has(current.status) || terminal.has(current.status)) return current;
    await new Promise((resolve) => setTimeout(resolve, 1500));
  }
}

The list call is Get user orders. What each status means is Order lifecycle.

Cancel a managed limit order

Cancel is POST /execution/orders/cancel with orderId. Queued and open orders can be cancelled. A terminal order cannot.

SDK
const cancel = await client.cancelManagedOrder(orderId);

Cancel an order.

cancel_pending means the venue has not confirmed yet. Keep polling until cancelled, or until filled or partial_fill if a fill won the race. Do not stop on open.

Self-custody cancels are on the signing page

Build the Polymarket ClobAuth signature or Hyperliquid cancel action using Self-custody signing.

Pass that signature as cancelSignature to cancelManagedOrder(orderId, { cancelSignature }). An order a linked wallet placed on Polymarket or Hyperliquid outside sufwan-local-test uses cancelVenueOrder with venue, venueOrderId, signerAddress, and cancelSignature. Both are the same cancel route. sufwan-local-test cannot cancel a self-custody order without a new signature, and it does not retry one on its own. GTD expiry is enforced by the venue. sufwan-local-test does not cancel self-custody GTC orders when a market closes.

On this page