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.
How It Works
Section titled “How It Works”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.
Builder Header
Section titled “Builder Header”Every builder endpoint requires this additional header:
| Header | Description |
|---|---|
OPENFISH_BUILDER_ID | Your 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.
POST /builder/order
Section titled “POST /builder/order”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 credentialslet 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 automaticallylet 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:
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": []}POST /builder/cancel
Section titled “POST /builder/cancel”Cancel an attributed order. Functions like DELETE /order but requires the builder header.
let resp = builder_client.cancel_order("ORDER_ID").await?;REST:
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"}Database Persistence
Section titled “Database Persistence”The builder_id is stored in two places:
Orders Table
Section titled “Orders Table”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.
Trades Table
Section titled “Trades Table”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.
Remote Builder Signing
Section titled “Remote Builder Signing”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 Key Management
Section titled “Builder API Key Management”Create or Derive Builder API Key
Section titled “Create or Derive Builder API Key”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).
Revoke Builder API Key
Section titled “Revoke Builder API Key”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.
Verifying Attribution
Section titled “Verifying Attribution”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);}Next Steps
Section titled “Next Steps”- Create Orders — Standard order creation -> create
- Builder Methods — Full builder client API -> ../clients/builder