> ## 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.

# Place HIP-4 orders, cancel, and convert outcome tokens

> PredictionTradingAdapter API: place limit and market orders on HIP-4 outcomes, cancel resting orders, and run split, merge, and negate conversions.

The Trading Adapter (`adapter.trading`) lets you submit and cancel orders on HIP-4 prediction market outcomes. Before you can place any orders you must authenticate using `adapter.auth.initAuth()` - the trading adapter returns an error result (rather than throwing) if you attempt to place an order without a valid signer. Order placement performs local validation for tick alignment, notional size, and minimum shares before signing, so most errors are caught before hitting the exchange.

<Warning>
  You must call `adapter.auth.initAuth(walletAddress, signer)` before using any
  trading methods. See the [Auth Adapter](/sdk/reference/auth-adapter) page for
  the full authentication flow, including agent key setup.
</Warning>

***

## `placeOrder(params)`

Places a single order on a HIP-4 outcome. This method **never throws** - check the `success` field and the `error` message on the returned result instead.

For limit orders, the SDK performs these local checks before signing:

* Tick-aligns the price to 5 significant figures.
* Validates that the notional value (`price × amount`) meets the minimum (10 USDH) unless `skipMinNotionalCheck` is `true`.
* When `markPx` is provided, validates that `amount` meets the minimum shares threshold.

For market orders, the SDK fetches the current mid and applies 8% slippage (ceiling for buys, floor for sells), then clamps the result to `[0.0001, 0.9999]`.

```typescript theme={null}
const result = await adapter.trading.placeOrder({
  marketId: "516",
  outcome: "#5160",
  side: "buy",
  type: "limit",
  price: "0.65",
  amount: "100",
});

if (!result.success) {
  console.error(result.error);
}
```

### Parameters

<ParamField path="marketId" type="string" required>
  The outcome ID as a string (e.g. `"516"`).
</ParamField>

<ParamField path="outcome" type="string" required>
  The side identifier. Use the coin string (e.g. `"#5160"` for side 0, `"#5161"`
  for side 1). This determines which side of the outcome you are trading.
</ParamField>

<ParamField path="side" type="string" required>
  `"buy"` or `"sell"`.
</ParamField>

<ParamField path="type" type="string" required>
  `"market"` or `"limit"`.
</ParamField>

<ParamField path="price" type="string">
  Limit price as a decimal string (0–1). Required for limit orders. Ignored for
  market orders (the SDK computes price from the mid with slippage).
</ParamField>

<ParamField path="amount" type="string" required>
  Order size in shares.
</ParamField>

<ParamField path="timeInForce" type="string" default="GTC">
  Time-in-force for limit orders. One of `"GTC"`, `"GTD"`, `"FOK"`, `"FAK"`. See
  the TIF mapping table below. Ignored for market orders.
</ParamField>

<ParamField path="markPx" type="number">
  Current market price (0–1). When provided, the SDK enforces a minimum shares check: `amount >= getMinShares(markPx)`, which ensures the order meets the 10 USDH minimum notional.
</ParamField>

<ParamField path="builderAddress" type="string">
  Optional referral address that receives builder fees. Checksummed addresses
  are accepted; the SDK lowercases the value before signing.
</ParamField>

<ParamField path="builderFee" type="number">
  Builder fee in tenths of a basis point. `0` = no fee. `100` = 0.1%. Maximum is
  `1000` (1.0%).
</ParamField>

<ParamField path="skipMinNotionalCheck" type="boolean">
  When `true`, skips the SDK's local minimum-notional and minimum-shares
  pre-checks. Use this for position-closing flows where the residual size may be
  below 10 USDH but the exchange still accepts the order.
</ParamField>

### TIF mapping

The SDK maps SDK-level TIF values to Hyperliquid order types as follows:

| SDK `timeInForce` | Order `type` | Hyperliquid TIF  | Notes                               |
| ----------------- | ------------ | ---------------- | ----------------------------------- |
| `"GTC"` (default) | `"limit"`    | `Gtc`            | Good-till-cancelled                 |
| `"GTD"`           | `"limit"`    | `Gtc`            | No true GTD support; behaves as GTC |
| `"FOK"`           | `"limit"`    | `Ioc`            | True FOK semantics not enforced     |
| `"FAK"`           | `"limit"`    | `Ioc`            | Immediate-or-cancel                 |
| *(any)*           | `"market"`   | `FrontendMarket` | Slippage-protected best-execution   |

### Return type

`Promise<PredictionOrderResult>` - never throws.

<ResponseField name="success" type="boolean" required>
  `true` if the order was accepted by the exchange.
</ResponseField>

<ResponseField name="orderId" type="string">
  Hyperliquid order ID. Present when the order filled or is resting.
</ResponseField>

<ResponseField name="status" type="string">
  `"filled"` - fully executed. `"resting"` - sitting in the book. `"error"` -
  exchange rejected the order. `"unknown"` - unrecognized response.
</ResponseField>

<ResponseField name="shares" type="string">
  Filled size. Only present when `status === "filled"`.
</ResponseField>

<ResponseField name="error" type="string">
  Error message. Present when `success === false`.
</ResponseField>

### Examples

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

const adapter = createHIP4Adapter({ testnet: false });
await adapter.initialize();

// --- Authentication (agent key flow) ---
const agentKey = generatePrivateKey();
const agent = privateKeyToAccount(agentKey);
const typedData = getAgentApprovalTypedData(
  agent.address,
  "My App",
  Date.now(),
  false,
);
const sig = await walletClient.signTypedData(typedData);
await submitAgentApproval(sig, agent.address, "My App", Date.now(), false);
await adapter.auth.initAuth(userAddress, agent);

// --- Place a limit buy order ---
const limitResult = await adapter.trading.placeOrder({
  marketId: "516",
  outcome: "#5160", // side 0 (e.g. "Yes")
  side: "buy",
  type: "limit",
  price: "0.65",
  amount: "100",
  timeInForce: "GTC",
  markPx: 0.65, // enables minimum-shares validation
});

if (limitResult.success) {
  console.log("Order status:", limitResult.status);
  // "filled" or "resting"
  if (limitResult.status === "filled") {
    console.log("Filled shares:", limitResult.shares);
  }
} else {
  console.error("Order failed:", limitResult.error);
}

// --- Place a market sell order ---
const marketResult = await adapter.trading.placeOrder({
  marketId: "516",
  outcome: "#5161", // side 1 (e.g. "No")
  side: "sell",
  type: "market",
  amount: "50",
});

// --- Limit buy with builder fee ---
const builderResult = await adapter.trading.placeOrder({
  marketId: "516",
  outcome: "#5160",
  side: "buy",
  type: "limit",
  price: "0.70",
  amount: "25",
  builderAddress: "0xYourBuilderAddress",
  builderFee: 100, // 0.1%
});

// --- Close a small residual position, skipping notional check ---
const closeResult = await adapter.trading.placeOrder({
  marketId: "516",
  outcome: "#5160",
  side: "sell",
  type: "limit",
  price: "0.60",
  amount: "3",
  skipMinNotionalCheck: true,
});
```

***

## `cancelOrder(params[])`

Cancels one or more resting orders. Unlike `placeOrder`, this method **throws on failure**.

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

### Parameters

`cancelOrder` accepts an **array** of cancel requests:

<ParamField path="[].marketId" type="string" required>
  The outcome ID as a string.
</ParamField>

<ParamField path="[].orderId" type="string" required>
  The Hyperliquid order ID to cancel. Obtain this from `placeOrder`'s `orderId`
  field or from `adapter.account.fetchOpenOrders()`.
</ParamField>

<ParamField path="[].outcome" type="string">
  Optional side identifier (e.g. `"#5160"`). Providing this resolves the correct
  side asset ID. When omitted, the SDK defaults to side 0.
</ParamField>

### Return type

`Promise<void>` - resolves when all cancels are confirmed. Throws if authentication is missing or the exchange rejects the request.

<Warning>
  The current implementation always resolves the cancel asset ID to **side 0**
  unless you provide the `outcome` field. If you placed an order on side 1
  (`"#5161"`), you must pass `outcome: "#5161"` to cancel it correctly.
</Warning>

### Example

```typescript theme={null}
// Cancel a single resting order
await adapter.trading.cancelOrder([
  { marketId: "516", orderId: "12345", outcome: "#5160" },
]);

// Cancel multiple orders at once
await adapter.trading.cancelOrder([
  { marketId: "516", orderId: "12345", outcome: "#5160" },
  { marketId: "516", orderId: "67890", outcome: "#5161" },
]);

// Fetch open orders first, then cancel them
const openOrders = await adapter.account.fetchOpenOrders(userAddress);
const cancels = openOrders.map((order) => ({
  marketId: String(order.outcomeId),
  orderId: String(order.oid),
  outcome: order.coin,
}));
await adapter.trading.cancelOrder(cancels);
```

***

## Token conversions

Four protocol-level share-conversion primitives that move value between USDH and outcome tokens without touching the orderbook. They settle directly against your spot balances at the bundle-equivalence mint price - no spread paid, no slippage, no liquidity required. See the [Converting tokens concept](/platform/trading/converting) for the math, and the [Token conversions guide](/sdk/guides/conversions) for end-to-end code.

All four methods use the same `userOutcome` action envelope, sign via L1 agent signing, and return a `WalletActionResult`. None of them throw.

```typescript theme={null}
type WalletActionResult = {
  success: boolean;
  error?: string;
  filledSz?: string;
  avgPx?: string;
  oid?: number;
};
```

<Warning>
  Conversions affect your spot balances immediately on `success: true`. The SDK
  does not preview or simulate. Validate inputs before calling.
</Warning>

***

## `splitOutcome(params)`

Burn `X` USDH and mint `X` Yes shares + `X` No shares of one outcome.

```typescript theme={null}
await adapter.trading.splitOutcome({
  outcome: 5160,
  amount: "100",
});
```

### Parameters

<ParamField path="outcome" type="number" required>
  Numeric outcome ID. Matches `market.outcomeId` on a fetched HIP-4 market.
</ParamField>

<ParamField path="amount" type="string" required>
  USDH amount to split, as a decimal string (e.g. `"12.5"`). The SDK strips
  trailing zeros to match Hyperliquid's wire format. `X` USDH burned → `X` Yes +
  `X` No minted.
</ParamField>

### Return type

`Promise<WalletActionResult>` - never throws.

***

## `mergeOutcome(params)`

The inverse of `splitOutcome`. Burn `X` Yes + `X` No of one outcome and mint `X` USDH.

```typescript theme={null}
await adapter.trading.mergeOutcome({
  outcome: 5160,
  amount: "100",
});

// Or burn the max available:
await adapter.trading.mergeOutcome({ outcome: 5160, amount: null });
```

### Parameters

<ParamField path="outcome" type="number" required>
  Numeric outcome ID.
</ParamField>

<ParamField path="amount" type="string | null" required>
  Paired-share count to merge, as a decimal string. Pass `null` to merge the
  maximum available - the protocol burns `min(yes_balance, no_balance)` shares.
</ParamField>

### Return type

`Promise<WalletActionResult>` - never throws.

***

## `mergeQuestion(params)`

Burn `X` Yes shares from every outcome of a question (including the fallback) and mint `X` USDH. Lets you redeem a full Yes-bundle for collateral before the question resolves.

```typescript theme={null}
await adapter.trading.mergeQuestion({
  question: 42,
  amount: "10",
});

// Or redeem the max available:
await adapter.trading.mergeQuestion({ question: 42, amount: null });
```

### Parameters

<ParamField path="question" type="number" required>
  Numeric question ID. For multi-outcome and price-bucket markets, this is
  `market.questionId`. Default-binary markets have no parent question - use
  `mergeOutcome` instead.
</ParamField>

<ParamField path="amount" type="string | null" required>
  Yes-share count to redeem from each member outcome, as a decimal string. Pass
  `null` to redeem the maximum - `min(yes_balance)` across every outcome of the
  question.
</ParamField>

### Return type

`Promise<WalletActionResult>` - never throws.

***

## `negateOutcome(params)`

Burn `X` No shares of one outcome and mint `X` Yes shares of every **other** outcome in the same question (including the fallback). Converts "I don't think this wins" into "I think one of the others wins" without touching the orderbook.

```typescript theme={null}
await adapter.trading.negateOutcome({
  question: 42,
  outcome: 5160,
  amount: "5",
});
```

### Parameters

<ParamField path="question" type="number" required>
  Numeric question ID containing the source outcome.
</ParamField>

<ParamField path="outcome" type="number" required>
  Source outcome ID whose No shares are being converted. Must belong to
  `question`.
</ParamField>

<ParamField path="amount" type="string" required>
  No-share count to convert, as a decimal string. After the call, your No
  balance on `outcome` drops by `amount`, and you hold `amount` additional Yes
  shares of every other outcome under `question`.
</ParamField>

### Return type

`Promise<WalletActionResult>` - never throws.

<Note>
  The on-wire sub-action key is `negateOutcome`, matching Hyperliquid's testnet
  "Convert Outcomes" UI. Hyperliquid's docs body shows `negateQuestion` in
  places - that's a typo in their docs. The SDK sends `negateOutcome`.
</Note>
