> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outcome.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# How to place and cancel orders on HIP-4 markets

> Authenticate an agent key, place limit and market orders, handle results, cancel resting orders, and understand pricing and fee rules for HIP-4 trading.

Trading on HIP-4 markets follows a four-step flow: initialize an agent key, fetch the market and side you want to trade, place the order, and inspect the result. The SDK handles signing, price formatting, and notional validation automatically. `placeOrder` reports validation and exchange rejections in its result instead of throwing, so you can branch on `result.success`.

<Warning>
  You must call `hip4.auth.initAuth` before placing or cancelling any orders. Without it, `placeOrder` returns `{ success: false, error: "Not authenticated. Call auth.initAuth() first." }` and `cancelOrder` throws the same message.
</Warning>

## Place a limit order

<Steps>
  <Step title="Initialize authentication">
    Generate or load an agent key and pass it to `initAuth`. The agent key signs orders silently on behalf of the user's wallet.

    ```typescript theme={null}
    import { createHIP4Adapter } from "@outcome.xyz/hip4";
    import { privateKeyToAccount } from "viem/accounts";

    const hip4 = createHIP4Adapter({ testnet: true });
    await hip4.initialize();

    // The user's main wallet address (not the agent's address)
    const userAddress = "0xUserWalletAddress";

    // Load your pre-approved agent key
    const agentKey = process.env.AGENT_PRIVATE_KEY as `0x${string}`;
    const agent = privateKeyToAccount(agentKey);

    await hip4.auth.initAuth(userAddress, agent);
    ```

    <Note>
      The agent must be approved on-chain before trading. See the `auth-eoa.ts` example in the SDK repository for the full approval flow using `getAgentApprovalTypedData` and `submitAgentApproval`.
    </Note>
  </Step>

  <Step title="Fetch the market and side">
    Retrieve the market you want to trade. The `sides` array on each market object carries the pre-computed coin identifier you pass to `placeOrder`.

    ```typescript theme={null}
    import type { DefaultBinaryMarket } from "@outcome.xyz/hip4";

    const markets = await hip4.events.fetchMarkets({
      type: "defaultBinary",
    }) as DefaultBinaryMarket[];

    const market = markets[0];
    const yesSide = market.sides[0]; // { name: "Yes", coin: "#5160", asset: 100005160 }
    const noSide  = market.sides[1]; // { name: "No",  coin: "#5161", asset: 100005161 }
    ```
  </Step>

  <Step title="Place the limit order">
    Call `hip4.trading.placeOrder` with the market ID, the side coin, and your price and amount. Pass `markPx` to enable pre-submission min-shares validation.

    ```typescript theme={null}
    import { getMinShares } from "@outcome.xyz/hip4";

    // Fetch current mid price for validation
    const priceData = await hip4.marketData.fetchPrice(String(market.outcomeId));
    const markPx = parseFloat(priceData.outcomes[0]?.midpoint ?? "0.5");

    const result = await hip4.trading.placeOrder({
      marketId: String(market.outcomeId),
      outcome:  market.sides[0].coin,   // "#5160"
      side:     "buy",
      type:     "limit",
      price:    String(markPx),
      amount:   String(getMinShares(markPx)),
      markPx,                            // enables min-shares check before signing
      builderAddress: "0xYourAddress",   // optional: builder referral address
      builderFee: 100,                   // optional: 0.1% (100 tenths of a basis point)
    });
    ```
  </Step>

  <Step title="Check the result">
    Inspect `result.success` before reading other fields. The `status` field tells you whether the order filled immediately or is resting on the book.

    ```typescript theme={null}
    if (result.success) {
      console.log("Status:", result.status);    // "filled" | "resting"
      console.log("Order ID:", result.orderId);
      console.log("Shares filled:", result.shares);
    } else {
      console.error("Order rejected:", result.error);
    }
    ```

    When Hyperliquid rejects the whole request, `result.error` is `Exchange returned non-ok status`. From 1.3.0, `result.raw` carries Hyperliquid's own message, for example `User or API Wallet 0x... does not exist.` when the agent isn't approved.
  </Step>
</Steps>

## Place a market order

For a market order, set `type: "market"` and omit `price`. The SDK uses the `FrontendMarket` time-in-force with extreme prices (`0.99999` for buys, `0.00001` for sells) so the exchange handles best-execution.

```typescript theme={null}
const result = await hip4.trading.placeOrder({
  marketId: String(market.outcomeId),
  outcome: market.sides[0].coin,
  side: "buy",
  type: "market",
  amount: "20",
});

if (result.success) {
  console.log(
    `Filled ${result.shares ?? "?"} shares - status: ${result.status}`,
  );
} else {
  console.error("Failed:", result.error);
}
```

The SDK doesn't run its minimum-notional check on market orders. The exchange still enforces its own minimum.

## Cancel an order

Pass an array of cancel targets to `hip4.trading.cancelOrder`. Each target requires the `marketId` and `orderId`. Pass the `outcome` coin too, so the SDK resolves the correct side. Without it, the SDK cancels on side 0.

```typescript theme={null}
const res = await hip4.trading.cancelOrder([
  {
    marketId: "516",
    orderId: "12345",
    outcome: "#5160",
  },
]);

// One status per cancel: "success" or { error: string }
console.log(res.status, res.response?.data.statuses);
```

<Note>
  `cancelOrder` throws when you aren't authenticated, when you pass an empty array, or when the request itself fails (for example a network or HTTP error). Exchange-level rejections don't throw: they come back in the returned Hyperliquid response, as `status: "err"` or as an `{ error }` entry in `response.data.statuses`.
</Note>

## Order params reference

| Field | Type | Required | Description |
| - | - | - | - |
| `marketId` | `string` | Yes | Outcome ID as a string (e.g. `"516"`) |
| `outcome` | `string` | Yes | Side coin (e.g. `"#5160"`) |
| `side` | `"buy" \| "sell"` | Yes | Direction |
| `type` | `"limit" \| "market"` | Yes | Order type |
| `price` | `string` | Limit only | Limit price (e.g. `"0.65"`) |
| `amount` | `string` | Yes | Number of shares |
| `timeInForce` | `"GTC" \| "ALO" \| "FAK" \| "FOK" \| "GTD"` | No | Default: `"GTC"`. `"FOK"` and `"GTD"` aren't supported and throw |
| `markPx` | `number` | No | Current mark price; enables min-shares validation |
| `builderAddress` | `string` | No | Builder address that receives the builder fee |
| `builderFee` | `number` | No | Fee in tenths of basis points (`100` = 0.1%). Ignored when no builder address is set |
| `skipMinNotionalCheck` | `boolean` | No | Skip the SDK's local min-notional and min-shares checks (for closing small positions) |

## Pricing rules

**Tick-aligned prices**: the SDK rounds limit prices to the outcome price tick before signing: at most 5 significant figures and at most 5 decimals (a tick of `0.00001`). `"0.550012"` becomes `"0.55001"` and `"0.0123456"` becomes `"0.01235"`, so a price below 0.1 is never sent with 6 or more decimals. `placeOrder` and `placeOrders` both do this. To show or store the price the SDK will send, use the exported `formatOutcomePrice`:

```typescript theme={null}
import { formatOutcomePrice } from "@outcome.xyz/hip4";

const price = formatOutcomePrice("0.0123456"); // "0.01235"
```

**Min-notional**: the SDK rejects limit orders below the \$1 minimum notional (`MIN_NOTIONAL`) before signing or sending them. To use a stricter floor, pass `minOrderNotional` to `createHIP4Adapter`. Without `markPx`, the SDK measures notional as `price × amount`. With `markPx`, it uses the cheaper side's price, `amount × max(0.01, min(markPx, 1 - markPx))`, and also checks that `amount` is at least `getMinShares(markPx)`. Set `skipMinNotionalCheck: true` to skip both checks.

## Time-in-force options

| Value | Hyperliquid TIF | Behavior |
| - | - | - |
| `GTC` | `Gtc` | Good Til Cancelled. Default for limit orders; rests on the book until filled or cancelled |
| `ALO` | `Alo` | Add Liquidity Only (post-only). Hyperliquid cancels the order instead of letting it take liquidity |
| `FAK` | `Ioc` | Fill and Kill (immediate or cancel). Fills what it can now and cancels the rest |
| `FOK` | None | Not supported. `placeOrder` throws an error |
| `GTD` | None | Not supported. `placeOrder` throws an error |

Market orders always use `FrontendMarket` regardless of the `timeInForce` field.

## Builder fees

Builder fees let you attach a builder address and collect a fee on orders placed through your integration. Set `builderFee` in tenths of basis points (`100` = 0.1%, maximum `1000`) and provide `builderAddress`. A builder address without `builderFee` attaches the builder with a fee of 0. A `builderFee` is ignored when no builder address is set, either on the order or on the adapter.

```typescript theme={null}
const result = await hip4.trading.placeOrder({
  // ...other params
  builderAddress: "0xYourBuilderAddress",
  builderFee: 50, // 0.05%
});
```

You can also set `builderAddress` and `builderFee` once on `createHIP4Adapter`. Per-order values override them in `placeOrder`, while `placeOrders` always uses the adapter-level values. The user must first approve your builder's maximum fee. See [builder fee approval](/sdk/reference/auth-adapter#builder-fee-approval).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.