Skip to content

orders: fetch_builder_fee_rates lacks 404 handling — market order construction crashes on unregistered builder code #56

@Nexory

Description

@Nexory

fetch_builder_fee_rates / fetch_builder_fee_rates_sync in src/polymarket/_internal/actions/orders/market_data.py:75-92 issue GET /fees/builder-fees/<code> and don't translate or guard the 404 case. Callers in src/polymarket/_internal/actions/orders/market.py:185-217 invoke them unconditionally whenever params.builder_code is set and non-zero, so a market-buy with an unregistered builder code crashes during fee resolution and never reaches order construction.

Current code

# src/polymarket/_internal/actions/orders/market_data.py:75-92
async def fetch_builder_fee_rates(ctx: AsyncClientContext, *, builder_code: str) -> BuilderFeeRates:
    validated = validate_builder_code(builder_code)
    if validated == BYTES32_ZERO:
        raise UserInputError(
            "builder_code must be a real builder; zero (0x000…000) represents no attribution."
        )
    data = await ctx.clob.get_json(f"/fees/builder-fees/{validated}")
    return BuilderFeeRates.parse_response(data)


def fetch_builder_fee_rates_sync(ctx: SyncClientContext, *, builder_code: str) -> BuilderFeeRates:
    validated = validate_builder_code(builder_code)
    if validated == BYTES32_ZERO:
        raise UserInputError(...)
    data = ctx.clob.get_json(f"/fees/builder-fees/{validated}")
    return BuilderFeeRates.parse_response(data)
# src/polymarket/_internal/actions/orders/market.py:185-191
fee_info = await fetch_platform_fee_info(ctx, condition_id=condition_id)
builder_taker_fee_rate = Decimal(0)
if params.builder_code is not None and params.builder_code != BYTES32_ZERO:
    rates = await fetch_builder_fee_rates(ctx, builder_code=params.builder_code)
    builder_taker_fee_rate = rates.taker
return adjust_buy_amount_for_fees(...)

ctx.clob.get_json raises RequestRejectedError(status=404) on a missing builder code; the caller does not catch it and the entire market-order construction aborts.

Live server behavior

$ curl -s -w '\n%{http_code}\n' \
    'https://clob.polymarket.com/fees/builder-fees/0xabcdef…cdef'
{"error":"builder code not found"}
404

The 404 is well-defined ("builder code not found"). CORS is * so this is a server-state issue, not a transport restriction.

Same shape on clob-client-v2

Structurally the same as Polymarket/clob-client-v2#51 (ts/v2-transitional). Polymarket/clob-client-v2#72 by @Cassxbt proposes a silent try/catch fix — open since 2026-05-21 without maintainer engagement, so the design direction looks unresolved.

Design question

Three approaches:

  1. Silent skip: catch RequestRejectedError where status == 404 in fetch_builder_fee_rates, return BuilderFeeRates(maker=Decimal(0), taker=Decimal(0)). Order proceeds without builder attribution — silently. Bad UX for the case the caller actually wanted attribution.
  2. Typed error mapping: introduce UnknownBuilderCodeError(RequestRejectedError) (mirrors the existing pattern in polymarket/_internal/eoa/rpc.py:13: class JsonRpcCallError(RequestRejectedError):). fetch_builder_fee_rates re-raises 404 as UnknownBuilderCodeError; market-order callers can catch it and decide policy.
  3. Server-side: distinguish unknown-vs-disabled / not-yet-propagated.

I'd lean (2) — preserves information for the caller and matches the existing typed-error pattern. But this is a design call and could go differently depending on what the platform contract for "unknown builder code" is meant to be.

Impact

Limit orders are unaffected (they don't fetch fees in place.py). The break is scoped to market orders with builder attributionprepare_market_order_draft and the _resolve_market_buy_amount / _resolve_market_buy_amount_sync paths via market.py:185-217. A builder who pastes a code that isn't yet registered (testing, propagation lag, copy/paste of wrong env) gets a RequestRejectedError from a prepare_market_* call rather than from a fee lookup, which is hard to debug.

Cross-SDK

Same pattern in ts-sdk: Polymarket/ts-sdk#74 (packages/client/src/actions/clob.ts:398-409 + packages/client/src/actions/orders/market.ts:222-232).

Happy to PR once you've picked a direction.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions