Skip to content

Create Order

All Openfish orders are EIP-712 signed limit orders. The Rust SDK manages signing, fee lookup, and submission internally. If you prefer to talk to the REST API directly, you will need to assemble the signed order payload yourself.


Post a single signed order. L2 authentication required.

{
"order": {
"salt": "12345",
"maker": "0x...",
"signer": "0x...",
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": "71321045679252...",
"makerAmount": "50000000",
"takerAmount": "100000000",
"expiration": "0",
"nonce": "0",
"feeRateBps": "10",
"side": "BUY",
"signatureType": 0,
"signature": "0x..."
},
"orderType": "GTC",
"postOnly": false
}

The makerAmount and takerAmount are raw token amounts. For a BUY order, makerAmount is USDC spent and takerAmount is shares received. Price is derived as makerAmount / (makerAmount + takerAmount).

{
"orderID": "a1b2c3d4-...",
"status": "live",
"success": true,
"errorMsg": "",
"makingAmount": "50000000",
"takingAmount": "100000000",
"transactionsHashes": [],
"tradeIds": ["e5f6g7h8-..."]
}

Three steps — build, sign, submit:

use openfish_client_sdk::clob::types::Side;
use openfish_client_sdk::types::dec;
let token_id = "TOKEN_ID".parse()?;
// Step 1: Build the order (auto-fetches tick size, neg risk, fee rate)
let order = client
.limit_order()
.token_id(token_id)
.price(dec!(0.50))
.size(dec!(10))
.side(Side::Buy)
.build()
.await?;
// Step 2: Sign with your wallet key
let signed = client.sign(&signer, order).await?;
// Step 3: Submit
let response = client.post_order(signed).await?;
println!("Order ID: {}", response.order_id);
println!("Status: {:?}", response.status);

The EIP-712 domain for order signing uses name Openfish CTF Exchange and version 1.


GTD orders automatically expire at a chosen UTC timestamp. Set the expiration field to the desired Unix time.

use chrono::{TimeDelta, Utc};
use openfish_client_sdk::clob::types::OrderType;
let order = client
.limit_order()
.token_id(token_id)
.price(dec!(0.50))
.size(dec!(10))
.side(Side::Buy)
.order_type(OrderType::GTD)
.expiration(Utc::now() + TimeDelta::hours(1))
.build()
.await?;
let signed = client.sign(&signer, order).await?;
let response = client.post_order(signed).await?;

The matching engine runs periodic scans to detect and remove GTD orders that have passed their expiration.


These order types target immediate execution against resting liquidity.

use openfish_client_sdk::clob::types::{Amount, OrderType, Side};
let token_id = "TOKEN_ID".parse()?;
// FOK BUY: spend exactly $100 or cancel entirely
let buy = client
.market_order()
.token_id(token_id)
.amount(Amount::usdc(dec!(100))?)
.price(dec!(0.50)) // worst-price limit (slippage protection)
.side(Side::Buy)
.order_type(OrderType::FOK)
.build()
.await?;
let signed = client.sign(&signer, buy).await?;
client.post_order(signed).await?;
// FAK SELL: sell as many of 200 shares as possible at 0.45+
let sell = client
.market_order()
.token_id(token_id)
.amount(Amount::shares(dec!(200))?)
.price(dec!(0.45))
.side(Side::Sell)
.order_type(OrderType::FAK)
.build()
.await?;
let signed = client.sign(&signer, sell).await?;
client.post_order(signed).await?;

The price field on market orders acts as a worst-price limit for slippage protection, not a target execution price.


Post-only orders sit on the book without matching right away. If the order would cross the spread, the engine rejects it.

let order = client
.limit_order()
.token_id(token_id)
.price(dec!(0.48))
.size(dec!(100))
.side(Side::Buy)
.post_only(true)
.build()
.await?;
let signed = client.sign(&signer, order).await?;
let response = client.post_order(signed).await?;

Post-only is only valid with GTC and GTD order types. Combining it with FOK or FAK causes rejection.


Send up to 15 orders in one request. Each array element follows the same structure as POST /order.

let token_id = "TOKEN_ID".parse()?;
let bid = client
.limit_order()
.token_id(token_id)
.price(dec!(0.48))
.size(dec!(500))
.side(Side::Buy)
.build()
.await?;
let ask = client
.limit_order()
.token_id(token_id)
.price(dec!(0.52))
.size(dec!(500))
.side(Side::Sell)
.build()
.await?;
let signed_bid = client.sign(&signer, bid).await?;
let signed_ask = client.sign(&signer, ask).await?;
let responses = client.post_orders(vec![signed_bid, signed_ask]).await?;
for resp in &responses {
println!("{}: {:?}", resp.order_id, resp.status);
}

REST:

Terminal window
POST /orders
Content-Type: application/json
[
{
"order": { ... },
"orderType": "GTC"
},
{
"order": { ... },
"orderType": "GTC"
}
]

Each order in the batch is handled independently. A failure on one does not block the others.


The signed order conforms to this EIP-712 typed structure:

FieldTypeDescription
saltuint256Random salt for uniqueness
makeraddressFunder address (source of funds)
signeraddressWallet that signed the order
takeraddressRestricted taker (zero address for any)
tokenIduint256Outcome token ID
makerAmountuint256Amount the maker provides
takerAmountuint256Amount the maker receives
expirationuint256Unix expiration timestamp (0 for no expiry)
nonceuint256Order nonce
feeRateBpsuint256Fee rate in basis points
sideuint80 = BUY, 1 = SELL
signatureTypeuint80 = EOA, 1 = Proxy, 2 = GnosisSafe

Domain: { name: "Openfish CTF Exchange", version: "1", chainId: <chain_id> }


StatusDescription
liveOrder resting on the book
matchedOrder matched immediately
delayedMarketable order subject to delay (sports markets)
CANCELEDOrder rejected (post-only crossing, FOK not filled, validation failure)

  • Cancel Orders — Cancel single, batch, or all orders -> cancel
  • Attribution — Attribute orders to a builder -> attribution