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

# Global Markets

> Global Markets (via Hyperliquid) lets your users trade stocks, commodities, and forex as perpetual futures on-chain.

## Why This Matters

Building a global-markets trading product on-chain means integrating with perp DEXes, handling EIP-712 signing flows, managing deposits and withdrawals across bridges, and tracking positions with funding rates. We've done that - you get one API that covers everything to get your trading product live in days.

## What It Does

Let users trade stocks (AAPL, TSLA, NVDA, etc.), commodities (GOLD, OIL, NATGAS), and forex pairs (EUR, GBP, JPY) as perpetual futures. Build products that offer leveraged exposure to stock, commodity, and forex markets, portfolio diversification beyond crypto, or automated trading strategies - all through a single API.

You call our API, we return EIP-712 typed data. Your users sign off-chain (no gas needed for trades). Users trade real-world assets on-chain, you capture fees.

**How it works:** Global Markets runs on Hyperliquid's builder-deployed perpetual futures DEX. Users deposit USDC on Arbitrum, which bridges to Hyperliquid where they can trade perps tracking real-world asset prices.

## Supported Assets

Assets are organized into three categories:

| Category        | Examples                                            |
| --------------- | --------------------------------------------------- |
| **Stocks**      | AAPL, TSLA, NVDA, AMZN, GOOGL, META, MSFT, and more |
| **Commodities** | GOLD, SILVER, OIL, NATGAS, COPPER, URANIUM, CORN    |
| **Forex**       | EUR, GBP, JPY, AUD, CAD, CHF, CNH                   |

Use the `/v2/global_markets_perps/opportunities` endpoint to get the full live list with open interest, 24h volume, funding rates, and max leverage for each market.

## Getting Started

### 1. Deposit USDC

Depositing is a **two-step flow with two different actors**:

1. **Compass prepares the permit (gasless for the end-user).** Your client calls `POST /v2/global_markets_perps/deposit` and Compass returns an EIP-2612 permit struct. The end-user signs it off-chain — no gas, no Arbitrum tx from their wallet.
2. **You broadcast the bridge tx (you pay the gas).** With the signed permit, you call `batchedDepositWithPermit` on Hyperliquid's Bridge2 contract on Arbitrum (`0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7`) from a sponsor wallet **you** fund. Hyperliquid's validators watch Bridge2 events and credit the user's HL trading account automatically — no `/exchange` call is needed.

<Info>
  **The gas sponsor for the bridge tx is your wallet, not Compass.** Compass does not run a sponsor key on Arbitrum for this product. Pre-fund an Arbitrum hot wallet with enough ETH for bridge gas before going live, and keep its private key in your server-side secrets.
</Info>

#### Step 1 — get the permit

**Request** (`POST /v2/global_markets_perps/deposit`):

* `owner` — the end-user's EOA address on Arbitrum
* `amount` — USDC amount to deposit, human-readable (e.g. `"100"`)

**Response**:

* `permit` — EIP-712 typed data for the USDC permit; pass to the user's wallet for signing
* `amount_raw` — USDC amount in raw 6-decimal units; pass to the bridge call in Step 2
* `destination` — the address that will be credited on Hyperliquid (equals the deposit owner EOA — HL keys accounts by EVM address)

#### Step 2 — broadcast the bridge tx

Call `POST /v2/global_markets_perps/deposit/sponsor_prepare`. Compass returns a fully-encoded unsigned `Bridge2.batchedDepositWithPermit` tx. You sign and broadcast it from your sponsor wallet on Arbitrum — no ABI/encoding work on your side, no Compass-side key custody.

**Request**:

* `owner` — the user's EOA address on Arbitrum (the permit signer)
* `amount_raw` — from the `/deposit` response in Step 1
* `deadline` — from `permit.message.deadline` in the Step 1 response
* `signature` — the user's signed permit (65-byte hex, 0x-prefixed)
* `sender` — your sponsor wallet address on Arbitrum (sets `from` and `nonce` on the returned tx)

**Response**:

* `transaction` — an unsigned Arbitrum tx; sign with your sponsor key and submit via any RPC

<Warning>
  **Bridge2 can silently no-op without reverting.** A `success` receipt is not proof the deposit landed. After confirmation, scan `receipt.logs` for a USDC `Transfer` event (topic `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef`) from USDC on Arbitrum (`0xaf88d065e77c8cC2239327C5EDb3A432268e5831`) before telling the user the deposit succeeded.
</Warning>

### 2. One-Time Setup

Before placing the first trade, two one-time actions are needed:

**Enable Unified Account** - Call `/v2/global_markets_perps/enable_unified_account` to check and enable unified margin mode. If already enabled, returns `null` typed data (no action needed).

**Approve Builder Fee** - Call `/v2/global_markets_perps/approve_builder_fee` to authorize the builder address that should receive trading fees from this end-user's orders, naming a maximum fee rate. Sign the returned EIP-712 data and submit via `/execute`. The `builder` block is required on this endpoint — the Compass customer specifies the address and rate. The full setup, viewing flow, and troubleshooting live in the [Collecting Builder Fees](#collecting-builder-fees) section below.

### 3. Place Orders

**Market Order:**

<CodeGroup>
  ```python Python theme={"system"}
  from compass_api_sdk import CompassAPI

  with CompassAPI(api_key_auth="YOUR_API_KEY") as compass:
      # Prepare a market order to buy 1 AAPL perp
      order = compass.global_markets_perps.global_markets_perps_market_order(
          owner="0xYourWalletAddress",
          asset="AAPL",
          side="buy",
          size="1",
      )

      # order.typed_data contains EIP-712 data for the user to sign
      # After signing, submit via /execute endpoint
  ```

  ```typescript TypeScript theme={"system"}
  import { CompassApiSDK } from "@compass-labs/api-sdk";

  const compass = new CompassApiSDK({ apiKeyAuth: "YOUR_API_KEY" });

  // Prepare a market order to buy 1 AAPL perp
  const order = await compass.globalMarketsPerps.globalMarketsPerpsMarketOrder({
    owner: "0xYourWalletAddress",
    asset: "AAPL",
    side: "buy",
    size: "1",
  });

  // order.typedData contains EIP-712 data for the user to sign
  // After signing, submit via /execute endpoint
  ```
</CodeGroup>

**Limit Order:**

Call `/v2/global_markets_perps/limit_order` with a `price` parameter and optional `time_in_force` (GTC or ALO). Same sign-and-execute flow.

**Cancel Order:**

Call `/v2/global_markets_perps/cancel_order` with the order ID to cancel an open limit order.

### 4. Execute Signed Actions

All prepare endpoints (market\_order, limit\_order, cancel\_order, withdraw, approve\_builder\_fee) return EIP-712 typed data. After the user signs, submit the signature to the execute endpoint:

<CodeGroup>
  ```python Python theme={"system"}
  from compass_api_sdk import CompassAPI

  with CompassAPI(api_key_auth="YOUR_API_KEY") as compass:
      result = compass.global_markets_perps.global_markets_perps_execute(
          action=order.action,
          nonce=order.nonce,
          signature="0xUserSignature...",
      )
  ```

  ```typescript TypeScript theme={"system"}
  import { CompassApiSDK } from "@compass-labs/api-sdk";

  const compass = new CompassApiSDK({ apiKeyAuth: "YOUR_API_KEY" });

  const result = await compass.globalMarketsPerps.globalMarketsPerpsExecute({
    action: order.action,
    nonce: order.nonce,
    signature: "0xUserSignature...",
  });
  ```
</CodeGroup>

### 5. Track Positions

Call `/v2/global_markets_perps/positions` to get open positions with size, entry price, mark price, PnL, liquidation price, leverage, and cumulative funding.

<CodeGroup>
  ```python Python theme={"system"}
  from compass_api_sdk import CompassAPI

  with CompassAPI(api_key_auth="YOUR_API_KEY") as compass:
      positions = compass.global_markets_perps.global_markets_perps_positions(
          owner="0xYourWalletAddress",
      )

      for pos in positions.positions:
          print(f"{pos.asset}: {pos.size} @ {pos.entry_price} (PnL: {pos.unrealized_pnl})")
  ```

  ```typescript TypeScript theme={"system"}
  import { CompassApiSDK } from "@compass-labs/api-sdk";

  const compass = new CompassApiSDK({ apiKeyAuth: "YOUR_API_KEY" });

  const positions = await compass.globalMarketsPerps.globalMarketsPerpsPositions({
    owner: "0xYourWalletAddress",
  });

  for (const pos of positions.positions) {
    console.log(`${pos.asset}: ${pos.size} @ ${pos.entryPrice} (PnL: ${pos.unrealizedPnl})`);
  }
  ```
</CodeGroup>

## Collecting Builder Fees

When your users open positions, every fill on Hyperliquid charges a
trading fee. By attaching a `builder` block to each order, that fee is
routed to an address **you control**. You choose the rate (up to whatever
each end-user has authorized), Hyperliquid pays the fee on settlement,
and the USDC accumulates on your builder address — no custody, no
invoicing.

### Mental Model

Hyperliquid enforces builder fees with a strict two-rule consent model:

<Steps>
  <Step title="One-time consent (per end-user, per builder)">
    Before any order with a `builder` field will be accepted, the
    end-user must sign an `approveBuilderFee` action naming your
    **builder address** and the **maximum rate** they authorize. This
    signature is submitted once and recorded on the L1 order book.
  </Step>

  <Step title="Per-order opt-in">
    Each order that should pay a fee includes a `builder` block —
    `{address, max_fee_rate}`. The order's `max_fee_rate` must be ≤ what
    the user previously approved. Omit the block and the order pays *no*
    builder fee at all.
  </Step>
</Steps>

<Info>
  **Where do the trades actually happen?** Global Markets runs on
  Hyperliquid's **xyz HIP-3 builder DEX** — an off-chain order book on
  Hyperliquid L1. The fills and fee payments live entirely on L1, *not* on
  HyperEVM (HL's EVM chain). This is why you will not find any of this on
  Arbiscan / Etherscan / hyperevmscan.io — see [Where to see your fees](#where-to-see-your-fees).
</Info>

### Prerequisites

<Warning>
  **Your builder address must hold ≥100 USDC on its Hyperliquid perps
  account before any `approveBuilderFee` action will succeed.** This is a
  hard Hyperliquid rule. If the builder is underfunded, `/execute` responds
  with `502` and body `"Hyperliquid error: Builder has insufficient balance
    to be approved"`. Pre-fund the address once with at least 100 USDC and
  the rule is satisfied forever.
</Warning>

Checklist before your first trade:

1. **Builder address** — a regular EOA you control. Don't use a
   smart-contract address unless you've validated HL accepts it.
2. **Builder pre-funding** — deposit ≥100 USDC into the builder's
   Hyperliquid perps account. Use the HL UI, an L1 transfer from another
   HL account, or bridge from Arbitrum through Bridge2 + a
   `usdClassTransfer` action.
3. **End-user margin** — the end-user must have enough USDC margin on HL
   to actually place the order. Compass provides
   `/v2/global_markets_perps/deposit` for the Arbitrum → HL bridge step.

### TypeScript Walkthrough

Pin the SDK to a tested release and point at staging while validating:

<CodeGroup>
  ```bash Install theme={"system"}
  npm install @compass-labs/api-sdk@2.2.53-rc.0 viem
  ```

  ```typescript Setup theme={"system"}
  import { CompassApiSDK } from "@compass-labs/api-sdk";
  import { privateKeyToAccount } from "viem/accounts";

  const compass = new CompassApiSDK({
    apiKeyAuth: process.env.COMPASS_API_KEY!,
    serverURL: "https://staging.api.compasslabs.ai",
  });

  const endUser = privateKeyToAccount(
    process.env.END_USER_PRIVATE_KEY! as `0x${string}`,
  );

  const BUILDER_ADDRESS = "0xYOUR_BUILDER_ADDRESS";
  const MAX_FEE_RATE = "0.01%"; // 1 basis point
  ```
</CodeGroup>

**Step 1 — One-time builder approval**

```typescript theme={"system"}
const approve =
  await compass.globalMarketsPerps.globalMarketsPerpsApproveBuilderFee({
    owner: endUser.address,
    builder: { address: BUILDER_ADDRESS, maxFeeRate: MAX_FEE_RATE },
  });

const approveSig = await endUser.signTypedData(approve.typedData as any);

await compass.globalMarketsPerps.globalMarketsPerpsExecute({
  action: approve.action,
  nonce: approve.nonce,
  signature: approveSig,
});
```

Run this once per `(end-user, builder)` pair. To later charge a *higher*
rate, re-run with a larger `maxFeeRate` — the user signs again. Lower
rates require no re-approval.

**Step 2 — Place an order that pays the fee**

```typescript theme={"system"}
const order =
  await compass.globalMarketsPerps.globalMarketsPerpsMarketOrder({
    owner: endUser.address,
    asset: "AAPL",
    side: "buy",
    size: "0.1",
    slippagePercent: 1.0,
    reduceOnly: false,
    // Optional. Omit to place the order with no builder fee.
    builder: { address: BUILDER_ADDRESS, maxFeeRate: MAX_FEE_RATE },
  });

const orderSig = await endUser.signTypedData(order.typedData as any);

await compass.globalMarketsPerps.globalMarketsPerpsExecute({
  action: order.action,
  nonce: order.nonce,
  signature: orderSig,
});
```

The same `builder` field is accepted on `globalMarketsPerpsLimitOrder`.
`cancel_order` and `withdraw` do *not* take a builder field — fees are
charged on fills only.

<Tip>
  The `builder` field is **optional** on `market_order` and `limit_order`.
  Omit it to place a no-fee order — useful for testing, gifted trades, or
  running a no-monetization mode. It remains **required** on
  `approve_builder_fee`, since that endpoint's whole purpose is to record
  consent for a specific builder.
</Tip>

**Step 3 — Verify the fee was earmarked**

```typescript theme={"system"}
const activity = await compass.globalMarketsPerps.globalMarketsPerpsActivity(
  { owner: endUser.address, builder: BUILDER_ADDRESS },
);

console.log(activity.fills?.[0]);
// {
//   asset: "AAPL",
//   side: "buy",
//   price: "297.74",
//   size: "0.03",
//   fee: "0.001664",
//   builderFee: "0.000893",   // ← portion of the user's fee earmarked for you
//   ...
// }

console.log(activity.builderApproval);
// {
//   builder: "0xYOUR_BUILDER_ADDRESS",
//   max_fee_tenth_bps: 10,        // 0.01% (10 tenth-bps == 1 basis point)
//   approved: true,
// }
```

The `builderFee` figure here is what HL has earmarked. The actual
**credit** to your builder address may post later — see [Settlement
Timing](#settlement-timing).

### Where to See Your Fees

Because Hyperliquid trades live on an off-chain L1 order book, **there is
no Etherscan-style transaction trace for the fee payment**. Use the right
view for the level you care about:

**1. Lifetime totals — `userFees` info API.** Updates as soon as HL
recognizes the fee.

```bash theme={"system"}
# Lifetime fees earned (across all roles — taker, maker, builder)
curl -s -X POST https://api.hyperliquid.xyz/info \
  -H 'Content-Type: application/json' \
  -d '{"type":"userFees","user":"0xYOUR_BUILDER_ADDRESS"}' \
  | jq

# Same, scoped to the xyz HIP-3 DEX (where Global Markets trades live)
curl -s -X POST https://api.hyperliquid.xyz/info \
  -H 'Content-Type: application/json' \
  -d '{"type":"userFees","user":"0xYOUR_BUILDER_ADDRESS","dex":"xyz"}' \
  | jq
```

Look for `feesEarnedAsBuilder` / `builderFees` / similar — Hyperliquid
adds builder-specific roll-ups incrementally, exact key names vary by
release.

**2. Current spendable balance — `clearinghouseState`.**

<Warning>
  Balances on HIP-3 DEXes are **siloed per-DEX**. Builder fees from Global
  Markets accrue on the **xyz** DEX, not the main perps DEX. Query both —
  your main account looking unchanged is expected.
</Warning>

```bash theme={"system"}
# Main perps account — pre-funding, main-perps withdrawals
curl -s -X POST https://api.hyperliquid.xyz/info \
  -H 'Content-Type: application/json' \
  -d '{"type":"clearinghouseState","user":"0xYOUR_BUILDER_ADDRESS"}' \
  | jq '.marginSummary.accountValue, .withdrawable'

# xyz DEX account — where Global Markets builder fees land
curl -s -X POST https://api.hyperliquid.xyz/info \
  -H 'Content-Type: application/json' \
  -d '{"type":"clearinghouseState","user":"0xYOUR_BUILDER_ADDRESS","dex":"xyz"}' \
  | jq '.marginSummary.accountValue, .withdrawable'
```

**3. Per-event audit trail — `userNonFundingLedgerUpdates`.** Every
non-funding balance change on your builder address.

```bash theme={"system"}
curl -s -X POST https://api.hyperliquid.xyz/info \
  -H 'Content-Type: application/json' \
  -d '{
    "type":"userNonFundingLedgerUpdates",
    "user":"0xYOUR_BUILDER_ADDRESS",
    "startTime": 1779000000000
  }' \
  | jq
```

**4. Hyperliquid official portfolio UI.**
`https://app.hyperliquid.xyz/portfolio/0xYOUR_BUILDER_ADDRESS` — there's
a DEX selector at the top (defaults to "Main"); switch to **xyz** to see
balances there.

**5. Compass dashboard.** Every end-user attributed to your API key shows
up in the **Hyperliquid** tab at `https://compasslabs.ai/dashboard/hyperliquid`
with their positions, recent fills (each fill carries `builder_fee`), and
their current approval state.

### Settlement Timing

The `builderFee` field on a fill is **earmarked** the instant the trade
matches, but the **credit** to your builder address may post later:

* **Immediate** — credit appears in `clearinghouseState` (xyz-DEX-scoped)
  within seconds.
* **Hourly settlement** — HL settles certain HIP-3 builder-fee accruals
  at the funding interval (top of each UTC hour). If your trade was at
  `15:27 UTC` and you query at `15:35 UTC`, the credit may not have
  posted yet. Re-check after `16:00 UTC`.

`userFees` is the most reliable "did HL recognize the fee at all?"
signal — it ticks up regardless of when the credit posts to your balance.

### Economics

For a single fill:

* **`fee`** on the fill — total fee charged to the **end-user**, in USDC.
* **`builderFee`** on the fill — portion of `fee` earmarked for your
  builder, in USDC. Equals `fee × (builder.max_fee_rate /
  total_user_fee_rate)`.
* The remainder of `fee` accrues to Hyperliquid (validators / DEX
  operator).

At `max_fee_rate = "0.01%"` (1bp) the typical split is roughly:

| Component                  | Approximate share      |
| -------------------------- | ---------------------- |
| Builder fee → your address | \~54% of the gross fee |
| HL validator + xyz DEX fee | \~46% of the gross fee |

### Withdrawing Your Fees

Builder fees accumulate as USDC margin on your builder's HL perps
account (xyz-DEX-scoped for Global Markets). To move them to Arbitrum:

1. Transfer USDC from the xyz DEX-scoped perps account to the main perps
   account (HL `usdClassTransfer` with `dex: "xyz"`).
2. Withdraw from HL to Arbitrum via Compass's
   `/v2/global_markets_perps/withdraw` (sign + `/execute`), or via HL's
   own UI / Python SDK.
3. The withdrawal arrives as a Bridge2 USDC `Transfer` to your builder
   address on Arbitrum — **this** step shows up on Arbiscan.

### Troubleshooting

<AccordionGroup>
  <Accordion title="`/execute` returns 502 with 'Builder has insufficient balance to be approved'">
    Your builder address has less than 100 USDC margin on its HL perps
    account. Pre-fund it once with ≥100 USDC and retry. Verify with:

    ```bash theme={"system"}
    curl -s -X POST https://api.hyperliquid.xyz/info \
      -H 'Content-Type: application/json' \
      -d '{"type":"clearinghouseState","user":"0xYOUR_BUILDER_ADDRESS"}' \
      | jq '.marginSummary.accountValue'
    ```

    The runnable test script (see below) pre-flights this check before
    asking the user to sign anything.
  </Accordion>

  <Accordion title="`/execute` returns 502 with 'Builder fee not approved'">
    The end-user hasn't signed an `approveBuilderFee` for this builder
    address, or the rate on the order exceeds the approved max. Either
    re-run Step 1 with a higher `maxFeeRate`, or lower the per-order
    `maxFeeRate` on the order itself.

    Pre-check with `/activity?builder=…`:

    ```typescript theme={"system"}
    const a = await compass.globalMarketsPerps.globalMarketsPerpsActivity({
      owner: endUser.address,
      builder: BUILDER_ADDRESS,
    });
    console.log(a.builderApproval);
    // { builder: "0x...", max_fee_tenth_bps: 10, approved: true }
    ```
  </Accordion>

  <Accordion title="The fill shows `builderFee: 0.000893` but my builder balance hasn't moved">
    Two most likely causes:

    1. **DEX scope** — query `clearinghouseState` with `dex: "xyz"`. The
       fee landed on the xyz-DEX-scoped account, not main perps.
    2. **Settlement timing** — HL may settle the credit at the next
       hourly funding interval. Re-query after the next UTC hour rolls.

    The `userFees` info call is the canary: it ticks up as soon as HL
    recognizes the fee, regardless of where/when the balance credit
    posts.
  </Accordion>

  <Accordion title="I can't see the fee on Etherscan / Arbiscan / hyperevmscan">
    Expected — Global Markets trades happen on HL's off-chain L1 order
    book, not on any EVM chain. No EVM transaction is emitted for the
    fee. Use the HL `info` API queries or
    `https://app.hyperliquid.xyz/portfolio/0xYOUR_BUILDER_ADDRESS` (xyz
    DEX selector) instead.
  </Accordion>

  <Accordion title="My builder address is a smart contract / Safe / Privy wallet">
    HL builder approval is keyed to a regular EOA. Multisigs and contract
    wallets may not be honoured as builders. Use a controlled EOA whose
    private key you can rotate (e.g. a dedicated treasury EOA, or a
    sub-account on the customer's wallet provider).
  </Accordion>
</AccordionGroup>

### Runnable Test Script

A complete TypeScript script that exercises the full
approve-then-trade-then-verify flow against staging lives at
[`api/scripts/examples/builder-fees`](https://github.com/CompassLabs/mono/tree/main/api/scripts/examples/builder-fees)
in the monorepo. It pre-flights the builder-funding rule, signs both
typed-data payloads with viem, polls `/activity` for the resulting fill,
and prints the collected fee.

## Key Concepts

**Perpetual Futures** - These are not spot tokens. Users trade perps that track real-world asset prices. Positions accrue funding rates and can be leveraged.

**EIP-712 Signing** - Unlike Yield or Credit which return unsigned Ethereum transactions, Global Markets returns EIP-712 typed data for Hyperliquid's off-chain order system. Users sign off-chain and submit signatures - no gas is needed for trades.

**Prepare + Execute Flow** - Every action follows a two-step flow: (1) call a prepare endpoint to get EIP-712 typed data, (2) user signs, (3) call `/execute` with the signature.

**USDC on Arbitrum** - Deposits and withdrawals use USDC on Arbitrum, bridged to/from Hyperliquid.

## Use Cases

**Brokerage App:** Build a stock trading app where users trade stock perps with crypto. Show familiar stock tickers, price charts, and portfolio views while executing trades on-chain.

*Example: User deposits 1,000 USDC, buys 2 AAPL perps at 200 each. AAPL rises 5%, user's position is worth 1,020. Close position for 20 profit.*

**Diversification Tool:** Let crypto-native users diversify into stock, commodity, and forex exposure without leaving the on-chain ecosystem. Offer one-click allocation across stocks, gold, and forex.

*Example: User allocates 10,000 USDC — 40% NVDA, 30% GOLD, 30% EUR — all as perps with a single deposit from their existing Arbitrum wallet.*

**Automated Trading:** Build bots that execute strategies across stock, commodity, and forex markets on-chain. Arbitrage between crypto and real-world asset perps, or run momentum strategies on stock perps.

## Next Steps

<CardGroup cols={3}>
  <Card title="Earn" icon="chart-line" href="/v2/Products/Earn">
    Earn yield on idle USDC while not trading.
  </Card>

  <Card title="Credit" icon="building-columns" href="/v2/Products/Credit">
    Borrow against crypto holdings for more trading capital.
  </Card>

  <Card title="Bridging" icon="bridge" href="/v2/Products/Bridging">
    Move assets across chains to fund your trading account.
  </Card>
</CardGroup>
