Skip to main content
Skip to main content

HyperCore perps via the API

This page walks through one HyperCore perps RFQ over the API: request quotes, read them, build and sign the order that settles your side of the trade, accept, and read the fill. Use it if you integrate directly as a taker. For how matched execution works and what outcomes to expect, see HyperCore perpetuals.

Login, request signing, and the socket work as in Your first trade. This flow adds one step: you sign a Hyperliquid order and send it with your accept. Build it exactly as Signing says. Silhouette answers the accept with 202 once the request parses, and it checks your order afterward. If it refuses the order, it gives you no reason: the quote you chose turns FAILED and the request stays PENDING.

Setup​

Set these up before your first request. HyperCore perpetuals setup explains each Hyperliquid step.

  • API credentials. Mint an accessKey and secret with the login flow, and sign every request as in Signing requests.
  • A signing key. Your Hyperliquid main account is the address you log in to Silhouette with. Sign your order with that address's key, or with an unexpired API wallet that address approved at Hyperliquid through approveAgent. The builder fee approval and the position are on that address too, and orders an API wallet signs book to it. If Silhouette cannot check the approval at Hyperliquid, it refuses your accept before it sends anything.
  • A builder fee approval at or above the rate. When a quote carries a builderFeeTenthBp, your main wallet (not an API wallet) signs Hyperliquid's approveBuilderFee for Silhouette's builder address. The approval's maxFeeRate is a percentage string: a rate of f tenths of a basis point is f / 1000 percent, so 10 is "0.01%". Approve at or above the rate on the quote. Silhouette reads your approval before it places anything at Hyperliquid. An approval below the rate ends the request with SETTLEMENT_FAILED. If the read gets no answer, Silhouette refuses your accept. See Hyperliquid's builder codes and Fees.
  • Time. Accept inside the maker's window and before the auction ends. See Deadlines.

Request​

Submit the request with POST /v1/rfq/requests. This example buys 0.01 BTC and accepts no more than 850 USDC for it:

curl https://rfq-api.silhouette.exchange/v1/rfq/requests \
--request POST \
--header "Content-Type: application/json" \
--header "Authorization: Bearer <access-key>" \
--header "Silhouette-API-Timestamp: <unix-ms>" \
--header "Silhouette-API-Signature: <base64-hmac>" \
--data '{
"instrumentId": "BTC-USDC-PERP",
"side": "BUY",
"baseQty": "0.01",
"quoteLimit": "850",
"windowMs": 30000,
"idempotencyKey": "0193b6f1-7c10-7d6c-8000-abc123456789"
}'

The 202 response carries the RFQ's identity and the auction deadline:

{
"rfqId": "rfq_...",
"status": "PENDING",
"auctionEndsAt": 1769600030000
}

What to set:

  • instrumentId is a perps ID such as BTC-USDC-PERP. GET /v1/rfq/instruments is the live list, where a perp's instrumentType is PERP.
  • baseQty is in Hyperliquid size units, on the coin's szDecimals grid.
  • quoteLimit: always send it. It equals your size times your limit price, so your per-unit limit is quoteLimit / baseQty. On a BUY, that limit caps the price you pay, and on a SELL it sets the least per unit you accept. A partial fill settles for a smaller total. Fees sit outside the limit.
  • autoAccept must be false or omitted. Silhouette refuses true on a perp with a 400 PERP_AUTO_ACCEPT_UNSUPPORTED, because you sign your order at acceptance.
  • idempotencyKey is a fresh UUID for each request. Reusing a key replays the old RFQ.

Unlike spot, a perps request locks no funds when you submit or when you accept.

Quotes​

Subscribe to the rfqStatus and quotes topics as in Your first trade, or list the quotes you can still accept with GET /v1/rfq/requests/{id}/quotes?status=SUBMITTED. A quote looks like this, trimmed to the fields this page uses:

{
"rfqId": "rfq_...",
"quoteId": "qt_...",
"version": 1,
"instrumentId": "BTC-USDC-PERP",
"side": "BUY",
"status": "SUBMITTED",
"receivedAt": 1769600005000,
"acceptableForMs": 30000,
"makerPays": { "token": "BTC", "amount": "0.01" },
"makerReceives": { "token": "USDC", "amount": "840" },
"takerTotal": "840",
"builderFeeTenthBp": 14
}

builderFeeTenthBp is in tenths of a basis point. The current rate is on the Fees page.

What to do with each field:

  • makerPays, makerReceives: the two legs, which give you the size and the price of your order. On a BUY, makerPays is the size you receive and makerReceives is the USDC you pay. On a SELL, the legs swap.
  • builderFeeTenthBp: the builder fee rate. When present, your order must carry it. When absent, there is no fee and your order carries no builder key. Silhouette reads the rate when it shows the quote and again when you accept, and refuses your accept if the rate changed in between.
  • version: send it back as expectedVersion. A maker that re-prices keeps the quoteId and advances the version, so sign a new order for the new terms.
  • acceptableForMs: how long the maker's quote stays acceptable, counted from receivedAt. Absent when the maker named no window.
  • takerTotal: do not build your order from it. On a perps SELL, takerTotal holds the size, not USDC.

A refused accept does not end the request. The quote you chose turns FAILED, the request stays PENDING, and you can accept another quote before auctionEndsAt. Watch the quote's status as well as the request's. The accept call in the API Reference lists the error codes it can return.

Signing​

You sign a Hyperliquid order for the quote you chose. Silhouette recovers the signer from your signature, checks the order against the quote, and broadcasts it once, as you signed it. Silhouette returns no reason for a refused order, so follow every rule below.

  1. Envelope. Send { action, nonce, signature }, where signature is { r, s, v }. r and s are 0x plus 64 hex characters, and v is 27 or 28. The envelope has no sender field, because Silhouette recovers the signer from the signature.
  2. No vaultAddress and no expiresAfter. Silhouette refuses an order with vaultAddress. It drops expiresAfter without warning, so it hashes a shorter message and recovers the wrong key.
  3. Key order. The action's keys go in this order: type, orders, grouping, builder. Each order's keys go in this order: a, b, p, s, r, t, c. Any other key order changes the signed hash. Set type to "order", send exactly one order, and set grouping to "na".
  4. a is the asset index. Use the coin's zero-based position in the universe array of Hyperliquid's {"type":"meta"} response, matched by exact name to the first part of the instrumentId (BTC-USDC-PERP gives BTC). Stay on the main perp dex, and send no dex parameter. Do not filter any coin out of the array, even one Hyperliquid no longer trades, because every coin keeps its slot. Read the index again on each accept. Silhouette's API does not return the index. See Hyperliquid's asset IDs.
  5. b is your side. Send true on a BUY and false on a SELL. The maker's order is on the opposite side.
  6. s is the size. Copy the base leg to the digit: makerPays.amount on a BUY, makerReceives.amount on a SELL. It sits on the coin's szDecimals grid and equals the quoted size exactly.
  7. p is the price. The quote leg is makerReceives.amount on a BUY and makerPays.amount on a SELL. Divide it by s, then truncate the result to the decimals Hyperliquid allows (see Hyperliquid's tick and lot size rules). p times s must land within one USDC micro-unit of the quote leg. Never derive p from takerTotal.
  8. p and s are canonical decimal strings. Strip trailing zeros ("84000", never "84000.0") and never use exponent notation. Silhouette re-serializes both in this form before it recovers the signer, so any other form recovers the wrong key.
  9. t is the order type, and r is your reduce-only flag. Set t to { "limit": { "tif": "Ioc" } }, an immediate-or-cancel (IOC) order. Set r to false for an ordinary order, or true for an order that can only shrink your position. Hyperliquid checks a reduce-only order against your position when it executes, so it can fill part or none of the accepted size. See Closing a position.
  10. c is your client order ID (cloid). Use 16 random bytes as lowercase 0x hex, because uppercase hex changes the hash. Silhouette's checks do not require a cloid, but always set one. Without it, an unclear outcome can only end as EXECUTION_UNCONFIRMED, and you have no key to look the order up at Hyperliquid.
  11. builder appears only when the quote has a rate. When the quote carries builderFeeTenthBp, add builder as { b, f }. b is Silhouette's builder address for the environment, in lowercase, and f is the quote's rate exactly. When builderFeeTenthBp is absent, the order carries no builder key: not null, and not zero. Silhouette compares the value exactly.
  12. The nonce is Unix milliseconds. Mint it immediately before you sign, and never reuse it. Silhouette claims each signer and nonce pair once across all your RFQs. A reused nonce does nothing: the quote stays live and the request stays PENDING, so sign a fresh nonce. See Hyperliquid's nonce rules.
  13. Signing. Sign the action as a Hyperliquid L1 action (see Hyperliquid's signing notes). The typed data is Hyperliquid's L1 Agent message (source, connectionId) under the domain name Exchange, version 1, chain ID 1337, and a zero verifying contract. connectionId is the keccak hash of the msgpack-encoded action, the nonce as eight big-endian bytes, and a 0x00 byte, joined in that order.
  14. Network and signer. The source is "a" on mainnet and "b" on testnet, and it must match the Hyperliquid network your Silhouette environment settles on. Production (rfq-api.silhouette.exchange) settles on Hyperliquid mainnet, so sign with isTestnet false. Sign with the key of the address you log in to Silhouette with, or with an API wallet that address approved, because the builder fee approval and the position are on that address too.

The API has no endpoint for the builder address. Silhouette publishes it out of band, and the order must carry it exactly, in lowercase, because the signature commits to it.

Acceptance​

Record your cloid with the rfqId and quoteId before you send the accept. Silhouette returns no Hyperliquid identifier, so the cloid is the only key for looking the order up later.

Send the quote's version as expectedVersion, and the signed order as perpTakerOrder:

curl https://rfq-api.silhouette.exchange/v1/rfq/requests/rfq_.../accept \
--request POST \
--header "Content-Type: application/json" \
--header "Authorization: Bearer <access-key>" \
--header "Silhouette-API-Timestamp: <unix-ms>" \
--header "Silhouette-API-Signature: <base64-hmac>" \
--data '{
"quoteId": "qt_...",
"expectedVersion": 1,
"perpTakerOrder": {
"action": {
"type": "order",
"orders": [
{
"a": 0,
"b": true,
"p": "84000",
"s": "0.01",
"r": false,
"t": { "limit": { "tif": "Ioc" } },
"c": "0x3f9a1c5e7b2d4a6f8c0e1b3d5f7a9c2e"
}
],
"grouping": "na",
"builder": { "b": "<lowercase-builder-address>", "f": 14 }
},
"nonce": 1769600006000,
"signature": { "r": "0x...", "s": "0x...", "v": 27 }
}
}'

The accept returns 202 with the request still PENDING, and the response carries no outcome. If the maker re-priced since you read the quote, the accept fails with a 409 QUOTE_VERSION_MISMATCH. Read the quotes again, sign a new order for the new terms, and accept with the new version.

Results​

The outcome arrives later on the rfqStatus topic. The RFQ moves to QUOTED when Silhouette starts placing the orders, and the final update carries the full RFQ in SETTLED or FAILED.

SETTLED. The RFQ carries no fill and no txHash. Read the fill from the winning quote over REST, with either call:

  • GET /v1/rfq/requests/{id}/quotes?status=SETTLED, where the winning quote is the SETTLED one.
  • GET /v1/rfq/quotes/{quoteId}, for the quote you accepted.

The quote reports perpFilledSize and perpFillAvgPx. Read again if the fields are absent. The quotes topic pushes nothing at settlement, and neither topic replays a settled RFQ when you subscribe. If your client disconnected before settlement, read the outcome over REST.

A partial fill still shows as SETTLED, so compare perpFilledSize with the size you accepted. perpFillAvgPx is the average price before fees. Hyperliquid's fee and the builder fee come on top.

Failures​

A failed RFQ is final and carries a failureCode. On HyperCore perps:

  • EXECUTION_NOT_FILLED and SETTLEMENT_FAILED end the request. After SETTLEMENT_FAILED, raise your builder fee approval at Hyperliquid to the rate first. For either code, submit a new request with a new idempotencyKey, and do not accept again on the failed one.
  • EXECUTION_UNCONFIRMED means a position may exist. Look the order up by your cloid, and never resubmit the same signed order.

After any failed perps request, check your Hyperliquid position before trading that market again. The RfqFailureCode schema in the API Reference lists every code, and Settlement shows how each outcome arises.

Verification​

Silhouette reports the fill, and Hyperliquid holds the record of it. Silhouette returns no Hyperliquid identifier for perps (no cloid, order ID, or hash), so your own cloid is the key. Look the order up by cloid with orderStatus, and its fills with userFillsByTime, on Hyperliquid's info endpoint, using your main account address.

Silhouette has no positions endpoint. Read your position from Hyperliquid with clearinghouseState. For the app, see Checking a fill.

Deadlines​

Read the first two deadlines from the API. The third has no field, so accept promptly.

  • auctionEndsAt is the instant quoting closes, returned when you submit the request. Accept before it.
  • acceptableForMs is how long the maker's quote stays acceptable, counted from receivedAt. Absent when the maker named no window, and the quote then stands until the auction closes.
  • The maker's signed order has to stay valid at Hyperliquid. The maker signed it when it quoted, and Hyperliquid rejects a signed order whose nonce is too old. A late accept can fail because Hyperliquid refuses the maker's order. In that case, Silhouette does not send your order, and the request ends FAILED with EXECUTION_NOT_FILLED.