client.Orders is the spot order surface. Methods take context.Context and return (result, error).
Account scope is an AccountScope argument (nil uses the client default subaccount). Import github.com/Fabric-Labs/polyester-sdk-go/models for request helpers.
Create/modify/batch paths wait for catalog hydration when enabled. You can still call client.WaitForCatalogs(ctx) before other decimal helpers.
PostOnly: true is rejected for market, limit IOC, and limit FOK
(*errors.ValidationError).Price on a market create is rejected by the SDK. Use MarketClientRefPrice when you need a
reservation / slippage reference. The server may still accept a stray price if you bypass the SDK.Create / BatchCreate synthesize Status: "accepted".
That is an admission ack, not a lifecycle state. Do not assert "created" on the create
response. Use ListOpen / Get / Subscribe for working /
partial / terminal statuses. Spot orders spend trading balance, not funding.Methods
| Method | Summary |
|---|---|
Create | Place a limit or market order. |
Modify | Patch price / qty / behavior / client id. |
Cancel | Cancel by order id or client order id. |
CancelAll | Cancel matching opens (dryRun). |
CancelAllAfter | Dead-man switch. |
BatchCreate / BatchReplace / GetBatchReplaceStatus / BatchCancel | Batch mutations and replace status. |
ListOpen / ListHistory | List orders. |
Get | One order + related trades. |
Subscribe | Private order stream (Messages() channel). |
WaitForOrderTradesComplete | Poll until terminal fills match CumQty. |
No GetDetails, use Get. No Recv, use Messages().
Create
symbol := "BTC-USDT"
tif := "gtc"
clientOrderID := "mm-bot-001"
price := models.PriceFromDecimal("64250.5")
result, err := client.Orders.Create(ctx, models.CreateOrderRequest{
Symbol: &symbol,
Side: "buy",
OrderType: "limit",
TIF: &tif,
Qty: models.QtyFromDecimal("0.25"),
Price: &price,
PostOnly: true,
ClientOrderID: &clientOrderID,
}, nil)
if err != nil { log.Fatal(err) }
fmt.Println(result.Status, result.OrderID) // Status == "accepted"CreateOrderRequest fields
| Field | Type | Required | Contract |
|---|---|---|---|
Symbol | *string | yes | Required for create; catalog hydration resolves decimal scales |
SymbolID | *uint32 | no | Model field exists, but symbol-ID-only create is not supported |
Side | string | yes | "buy" or "sell" |
OrderType | string | yes | "limit" or "market" |
TIF | *string | limit | "gtc", "ioc", or "fok" |
Qty | models.QtyInput | one sizing mode | Base quantity; exactly one of this or MaxQuoteDebitScaled |
MaxQuoteDebitScaled | *int64 | one sizing mode | Hard all-in quote budget for BUY market or limit IOC |
Price | *models.PriceInput | limit | Limit price; use PriceFromDecimal or ticks |
SubAccountID | *string | no | Explicit subaccount override |
ClientOrderID | *string | no | Account-scoped duplicate guard, 1โ36 allowed characters |
PostOnly | bool | no | true only for limit GTC |
FeeAsset | *string | no | "quote" default or BUY-only "base" |
ExpiresAt | *string | unsupported | Model field is not encoded by the current SDK |
AttachedRisk | map[string]any | unsupported | Non-nil input is rejected; it is never silently discarded |
MarketClientRefPrice | *models.PriceInput | market | Client reference price used for market reservation |
ClientOrderID and NewClientOrderID accept 1 to 36 ASCII letters, digits, ., _, :, /,
and -. Invalid values are rejected locally before send, including singular lookup and cancel by
client-order-id. A create request may omit the ID. Request IDs use the same character set and
accept 1 to 64 characters.
ClientOrderID is optional and the SDK does not generate one. A stable ID is strongly recommended
for every create that may need reconciliation after an unknown outcome. Reuse of a retained ID,
including an identical request, returns CONFLICT_DUPLICATE_CLIENT_ORDER_ID; it does not replay the
earlier result. Reconcile by client order ID before deciding whether to resubmit. Supplying the
current AttachedRisk map is rejected locally because it has no supported wire encoder; it is
never silently discarded.
FeeAsset replaces the prior fee-source / received vocabulary. Use "quote" or "base"; SELL
orders require "quote". A create response can include ResolvedBaseQtyScaled and, for
quote-budget sizing, SubmittedMaxQuoteDebitScaled. Call client.Orders.Preview(ctx, request, account) with the same request shape for an admissibility
check (Admissible, optional typed Rejection, resolved base size, and ProtectedPriceBound when price protection applied). Preview does not return fee or quote-debit estimates. Create
always re-evaluates the intent.
PreviewOrderResult exposes:
Admissible: whether the intent passed current admission checks.Rejection: optionalOrderErrorDetailwith a stableCodelabel such asBAD_QTYand field-levelViolations(FieldPath,RuleID,Message).ResolvedBaseQtyScaledand typedResolvedBaseQtywhen the service resolved a base size.ProtectedPriceBound: optional protective execution boundary, not an expected fill price.EvaluatedAtMs: evaluation time in Unix milliseconds.
Modify
newPrice := models.PriceFromDecimal("64100")
newQty := models.QtyFromDecimal("0.2")
requestID := "mod-1"
_, err = client.Orders.Modify(
ctx, nil, "BTC-USDT",
&result.OrderID, nil, nil, &requestID,
&newPrice, &newQty, nil, nil,
)Signature uses positional pointers for orderID / clientOrderID / subAccountID / requestID / newPrice / newQty / behavior / newClientOrderID. The exported Modify path does not accept attached-risk patches. If you pass a nil requestID,
the SDK generates one for that single call; supply a stable value when retrying the same logical
modify. See Requests & idempotency.
Modify parameters
| Parameter | Required | Contract |
|---|---|---|
ctx | yes | One overall cancellation/deadline context |
account | no | nil uses configured account scope |
symbol | yes | Pair symbol used for routing and quantity scale |
orderID / clientOrderID | exactly one | Existing order identity |
subAccountID | no | Explicit scope override |
requestID | no | SDK generates one when nil; provide and reuse one for retries |
newPrice / newQty | at least one | Replacement decimal/ticks or quantity input |
behavior | no | Backend modify behavior string |
newClientOrderID | no | Replacement client identity, locally validated |
Leave balance headroom when repricing a heavily reserved book. A replacement requires sufficient available balance for the new order. Reconcile after an ambiguous response before retrying or canceling and recreating the order.
Cancel / CancelAll / CancelAllAfter
_, err = client.Orders.Cancel(ctx, nil, nil, &clientOrderID, &symbol, nil, nil)
dry := true
preview, err := client.Orders.CancelAll(ctx, nil, nil, &symbol, nil, dry, nil)
fmt.Println(preview.MatchedOrders)
timeout := 15
_, err = client.Orders.CancelAllAfter(ctx, nil, timeout, nil, &symbol, nil, nil)If you pass a nil requestID on CancelAll / CancelAllAfter / batch helpers, the SDK generates
one for that single call; supply a stable value when retrying. Cancellation acknowledges
admission. Confirm the order has disappeared from ListOpen before releasing local state; retry
the same cancel if it remains visible after a bounded reconciliation window.
For process-owned cleanup, select owned client IDs and cancel them individually. A targeted cancel
that returns *errors.APIError code not_found is idempotent success because the order may have
filled or left the book after the preceding read; every other cancel error remains an error. Do
not use account-wide CancelAll as an ownership filter.
Pass exactly one of orderID and clientOrderID to Cancel and Get; both or neither are local
validation errors. Cancel with no symbol sends wire symbol_id=0 for directory routing. A supplied
symbol must resolve in hydrated catalogs, and supplying both symbol and symbolID is rejected. CancelAllAfter accepts timeoutSec=0 to disable or 10โ120 to arm; this range is enforced by
the API, not preflighted by the Go SDK.
CancelAll accepts only backend statuses submitted / dry_run; CancelAllAfter accepts armed / disabled. Empty or unknown statuses return *errors.ResponseContractError rather than an
ambiguous success result.
Batch
// Final bool is allowPartial, retained for compatibility and ignored on the wire.
batchID := "mm-batch-001"
batch, err := client.Orders.BatchCreate(ctx, nil, []models.CreateOrderRequest{
{
Symbol: &symbol,
Side: "buy",
OrderType: "limit",
TIF: &tif,
Qty: models.QtyFromDecimal("0.01"),
Price: &price,
PostOnly: true,
ClientOrderID: &batchID,
},
}, nil, &symbol, nil, false)
fmt.Println(batch.AcceptedCount, batch.RejectedCount)
cancelClientOrderID := "mm-a"
_, err = client.Orders.BatchCancel(ctx, nil, []models.BatchCancelItem{
{Key: models.OrderKeyByClientID(cancelClientOrderID)},
}, nil, nil)
newPrice := models.PriceFromDecimal("63900")
receipt, err := client.Orders.BatchReplace(ctx, nil, []models.BatchReplaceItem{
{Key: models.OrderKeyByClientID("mm-a"), NewPrice: &newPrice},
}, "BTC-USDT", nil, nil)
fmt.Println(receipt.BatchRequestID, receipt.Status, receipt.AcceptedCount)
status, err := client.Orders.GetBatchReplaceStatus(ctx, nil, receipt.BatchRequestID, nil)
fmt.Println(status.AdmissionStatus, len(status.Items))BatchReplace is a same-symbol quote refresh only (no per-item behavior, no behavior default, no allowPartial). The write RPC returns a durable admission receipt (BatchRequestID, accepted /
rejected counts), not a final execution outcome. After successful admission, predecessor order and client
IDs are stale. Switch immediately to each ReplacementOrderID and new client order ID in the
receipt. A predecessor Get may return not_found / ORDER_UNKNOWN; that is expected. Poll GetBatchReplaceStatus to reconcile admitted, working, rejected, and terminal. Status
can briefly return 404 after admission, so retry the poll.
For quote-refresh bots, models.IsBatchReplaceSettled(status) and models.BatchReplaceStatusSettled(status) mean every item is working, rejected, or terminal. They are reconciliation checkpoints, not a final execution outcome: working means the
successor is live. Reuse the same requestID for an ambiguous retry and never replace against a
stale predecessor.
Batch size contracts: BatchCreate max 20; BatchReplace / BatchCancel max 50.
These are API-side contracts; the Go SDK does not preflight every count. allowPartial on BatchCreate is accepted for source compatibility but is not encoded on the current wire.
Every successful batch-create response item is either accepted or rejected. The SDK reconciles
the aggregate counts for create, replace, and cancel batches with their per-item outcomes. A
malformed or inconsistent success response returns *errors.ResponseContractError instead of an
ambiguous result. Unknown rejection enums remain visible as UNKNOWN_ERROR_CODE(<number>).
Batch timeouts and reconciliation
A timeout on a batch mutation is an unknown outcome, not proof that nothing committed. Do not
blindly resubmit the batch with new identifiers. Give the batch a stable request ID, give every
create item a unique client order ID, and reconcile every item with Get, ListOpen, or order
history before retrying. The SDK sends each call once and does not promise server-side atomicity.
Trade projection after fills
Get can report CumQty before every fill is visible on the trades list. Prefer Orders.WaitForOrderTradesComplete after fills when you need trade rows to match CumQty:
orderID := "order-id-from-create"
got, err := client.Orders.WaitForOrderTradesComplete(
ctx, nil, &orderID, nil, nil, 15*time.Second,
)
if err != nil { log.Fatal(err) }
fmt.Println(len(got.Trades))Market BUY quantities may be normalized by the venue (for example 0.04 โ 0.03997). For
cleanup, use the completed trade projection and convert FeeAmountE18 to the symbol's base
quantity scale for BUY fills whose FeeAsset is "base"; subtract when FeeIsRebate is false
and add when it is true. Sell that net received base quantity, not the requested decimal or
gross CumQty.
Reads
orderID := "order-id-from-create"
open, err := client.Orders.ListOpen(ctx, nil, nil, nil, nil, true, false)
hist, err := client.Orders.ListHistory(ctx, nil, nil, &symbol, nil, nil, 100, false, false)
got, err := client.Orders.Get(ctx, nil, &orderID, nil, nil, true, false)
if got.Order != nil {
fmt.Println(got.Order.Status, len(got.Trades))
}
_ = open
_ = histSubscribe
sub, err := client.Orders.Subscribe(ctx, accountID)
if err != nil { log.Fatal(err) }
defer sub.Close()
for order := range sub.Messages() {
scale, ok := client.Catalogs.BaseQuantityScaleForSymbolID(order.SymbolID)
if !ok {
return fmt.Errorf("stream symbol %d quantity scale is unavailable", order.SymbolID)
}
leaves, err := order.LeavesQty.WithScale(scale).Format()
if err != nil { return err }
fmt.Println(order.Status, order.OrderID, leaves)
break
}
if err := sub.Err(); err != nil {
log.Printf("subscription ended: %v", err)
}Handshake completes before Subscribe returns. Overflow closes with *errors.QueueOverflowError.
Private order payloads may omit quantity scale metadata (Scale() == nil). Resolve scale from the
hydrated catalog by SymbolID (or the corresponding symbol) before formatting OrigQty, CumQty,
or LeavesQty. Never trust or invent a stream scale; fail closed when catalog lookup fails.
Order shape
Selected fields: OrderID, SymbolID, ClientOrderID, Side, Status, OrderType, TIF, OrigQty / CumQty / LeavesQty (QtyScaled), Price / AvgPx (PriceTicks), CreatedTsNs, PostOnly, AttachedRisk.
When includeAttachedRisk is set, decoded AttachedRisk.TrailingStop requires a positive
distance (ticks or bps). A trailing policy with missing/non-positive distance is omitted rather
than projected as a zero-distance stop. Trailing TriggerPriceSource / OrderType are not on the
wire and stay empty on decode.