Skip to content

SDK drift: hosted cancelOrder fires the network build request before validating a signer is configured in Python; TypeScript validates both signer and wallet address synchronously first #2152

Description

@realfishsam

Drift

For hosted-mode cancelOrder, TypeScript validates that a signer and a wallet address are both configured before making any network call. Python's equivalent validates the wallet address first, then immediately fires the cancel_order_build network request, and only discovers a missing signer after that round-trip completes — leaking the cancel intent to the server and wasting a request.

This is the inverse of the already-tracked #1502 ("hosted createOrder checks for a signer only after the remote build call in TypeScript; Python checks before any network call") — that issue is specific to createOrder. For cancelOrder, the languages swap roles: TypeScript is the one that front-loads the check, and Python is the one that defers it past the network call.

TypeScript SDK

sdks/typescript/pmxt/client.ts:2622-2633 (_hostedCancelOrder):

private async _hostedCancelOrder(orderId: string): Promise<Order> {
    const signer = this.requireHostedSigner();          // throws synchronously if no signer — no network call yet
    if (!this.walletAddress) {
        throw new MissingWalletAddress("hosted cancelOrder requires walletAddress");
    }
    const buildRequest = { order_id: orderId, user_address: this.walletAddress };
    const buildRoute = HOSTED_METHOD_ROUTES.get("cancelOrderBuild")!;
    const buildPayload = await _tradingRequest(this, {   // network call only after both checks pass
        method: buildRoute.method, path: buildRoute.path, body: buildRequest,
    }) as Record<string, unknown>;

requireHostedSigner() (client.ts:530-537) throws MissingWalletAddress immediately if this.signer is falsy.

Python SDK

sdks/python/pmxt/client.py:1008-1028 (_hosted_cancel_order):

def _hosted_cancel_order(self, order_id, signer=None) -> "Order":
    wallet_address = resolve_wallet_address(self)              # wallet checked first
    build_request = {"order_id": order_id, "user_address": wallet_address}
    build_payload = self._hosted_response_data(
        self._hosted_request("cancel_order_build", body=build_request)   # NETWORK CALL — signer not yet checked
    )
    ...
    signature = self._sign_hosted_typed_data(build_payload, "typed_data", route, wallet_address, build_request, signer=signer)

The signer check lives in _call_hosted_signer (client.py:884-887):

@staticmethod
def _call_hosted_signer(signer, typed_data):
    if signer is None:
        raise InvalidSignature("signer is required for hosted trading")   # only discovered here, after the round-trip

Expected

Python's _hosted_cancel_order should validate that a signer is configured before issuing the cancel_order_build request, matching TypeScript's synchronous front-loaded check (and matching the resolution direction #1502 will presumably pick for createOrder).

Impact

A Python caller invoking hosted cancel_order without a configured signer sends a real network request to cancel_order_build (revealing the order they intend to cancel to the server) before failing with InvalidSignature. TypeScript fails fast with no network I/O in the same scenario. Beyond the extra request, this is also an unnecessary side effect for any caller who accidentally omits a signer.


Found by automated SDK cross-language drift audit

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions