Skip to content

Order Attribution

Order attribution allows builders to stamp their orders and resulting trades with a builder_id. This identifier is recorded in both the orders and trades database tables, making it possible to measure volume, run analytics, and power reward programs for platforms routing orders through the Openfish CLOB.


Builder-attributed orders flow through dedicated endpoints (/builder/order, /builder/cancel) that expect an OPENFISH_BUILDER_ID header on top of standard L2 authentication. The server reads this header and attaches it to the order record and every trade that follows.

Aside from the attribution metadata, the order follows the exact same path as a standard POST /order — same matching engine, same balance validation, same fee logic.


Every builder endpoint requires this additional header:

HeaderDescription
OPENFISH_BUILDER_IDYour builder identifier string

If the header is absent or empty, the server responds with 400 Bad Request and the message OPENFISH_BUILDER_ID header required.


Submit an order tagged with builder attribution. The request body and response format mirror POST /order exactly.

use openfish_client_sdk::auth::builder::Config as BuilderConfig;
use openfish_client_sdk::auth::Credentials;
// Set up builder credentials
let builder_creds = Credentials::new(
std::env::var("OPENFISH_BUILDER_API_KEY")?.parse()?,
std::env::var("OPENFISH_BUILDER_SECRET")?,
std::env::var("OPENFISH_BUILDER_PASSPHRASE")?,
);
let builder_config = BuilderConfig::local(builder_creds);
let builder_client = client.promote_to_builder(builder_config).await?;
// Orders now include builder attribution automatically
let order = builder_client
.limit_order()
.token_id(token_id)
.price(dec!(0.50))
.size(dec!(10))
.side(Side::Buy)
.build()
.await?;
let signed = builder_client.sign(&signer, order).await?;
let response = builder_client.post_order(signed).await?;
println!("Order ID: {}", response.order_id);

REST:

Terminal window
curl -X POST "https://api.openfish.fun/builder/order" \
-H "Content-Type: application/json" \
-H "OPENFISH_ADDRESS: ..." \
-H "OPENFISH_SIGNATURE: ..." \
-H "OPENFISH_TIMESTAMP: ..." \
-H "OPENFISH_API_KEY: ..." \
-H "OPENFISH_PASSPHRASE: ..." \
-H "OPENFISH_BUILDER_ID: my-builder-platform" \
-d '{
"order": { ... },
"orderType": "GTC"
}'

Response:

{
"orderID": "a1b2c3d4-...",
"status": "live",
"success": true,
"builder_id": "my-builder-platform",
"tradeIds": []
}

Cancel an attributed order. Functions like DELETE /order but requires the builder header.

let resp = builder_client.cancel_order("ORDER_ID").await?;

REST:

Terminal window
curl -X POST "https://api.openfish.fun/builder/cancel" \
-H "Content-Type: application/json" \
-H "OPENFISH_ADDRESS: ..." \
-H "OPENFISH_SIGNATURE: ..." \
-H "OPENFISH_TIMESTAMP: ..." \
-H "OPENFISH_API_KEY: ..." \
-H "OPENFISH_PASSPHRASE: ..." \
-H "OPENFISH_BUILDER_ID: my-builder-platform" \
-d '{"orderID": "a1b2c3d4-..."}'

Response:

{
"canceled": ["a1b2c3d4-..."],
"notCanceled": {},
"builder_id": "my-builder-platform"
}

The builder_id is stored in two places:

When insert_order() is called from the builder route, the builder_id parameter receives the value from the OPENFISH_BUILDER_ID header. For non-builder orders, this field is an empty string.

When fills are processed, insert_trade() receives the same builder_id. This means every trade resulting from a builder-attributed order carries the builder’s identifier, enabling accurate volume attribution even when the trade involves a non-builder maker.


For production environments, keep builder credentials on a separate secure server and use remote signing:

let builder_config = BuilderConfig::remote(
"https://your-server.com/sign",
Some("optional-auth-token".to_owned()),
)?;
let builder_client = client.promote_to_builder(builder_config).await?;

The remote signing server receives { method, path, body, timestamp } and returns the HMAC builder headers:

{
"OPENFISH_BUILDER_API_KEY": "...",
"OPENFISH_BUILDER_TIMESTAMP": "...",
"OPENFISH_BUILDER_PASSPHRASE": "...",
"OPENFISH_BUILDER_SIGNATURE": "..."
}

Builder API keys are independent of user API keys. They follow the same HMAC signing scheme but use builder-specific headers (OPENFISH_BUILDER_API_KEY, OPENFISH_BUILDER_SIGNATURE, OPENFISH_BUILDER_TIMESTAMP, OPENFISH_BUILDER_PASSPHRASE).

If your builder credentials are compromised, revoke them immediately:

builder_client.revoke_builder_api_key().await?;

After revocation, generate new credentials and update your configuration.


Retrieve trades linked to your builder account:

use openfish_client_sdk::clob::types::request::TradesRequest;
let trades = builder_client.builder_trades(&TradesRequest::default(), None).await?;
for trade in &trades.data {
println!("{}: {} {} at {} (builder: {})",
trade.id, trade.side, trade.size, trade.price, trade.builder);
}