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
accessKeyandsecretwith 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'sapproveBuilderFeefor Silhouette's builder address. The approval'smaxFeeRateis a percentage string: a rate offtenths of a basis point isf / 1000percent, so10is"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 withSETTLEMENT_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:
instrumentIdis a perps ID such asBTC-USDC-PERP.GET /v1/rfq/instrumentsis the live list, where a perp'sinstrumentTypeisPERP.baseQtyis in Hyperliquid size units, on the coin'sszDecimalsgrid.quoteLimit: always send it. It equals your size times your limit price, so your per-unit limit isquoteLimit / baseQty. On aBUY, that limit caps the price you pay, and on aSELLit sets the least per unit you accept. A partial fill settles for a smaller total. Fees sit outside the limit.autoAcceptmust befalseor omitted. Silhouette refusestrueon a perp with a 400PERP_AUTO_ACCEPT_UNSUPPORTED, because you sign your order at acceptance.idempotencyKeyis 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 aBUY,makerPaysis the size you receive andmakerReceivesis the USDC you pay. On aSELL, 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 nobuilderkey. 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 asexpectedVersion. A maker that re-prices keeps thequoteIdand advances theversion, so sign a new order for the new terms.acceptableForMs: how long the maker's quote stays acceptable, counted fromreceivedAt. Absent when the maker named no window.takerTotal: do not build your order from it. On a perpsSELL,takerTotalholds 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.
- Envelope. Send
{ action, nonce, signature }, wheresignatureis{ r, s, v }.randsare0xplus 64 hex characters, andvis 27 or 28. The envelope has no sender field, because Silhouette recovers the signer from the signature. - No
vaultAddressand noexpiresAfter. Silhouette refuses an order withvaultAddress. It dropsexpiresAfterwithout warning, so it hashes a shorter message and recovers the wrong key. - 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. Settypeto"order", send exactly one order, and setgroupingto"na". ais the asset index. Use the coin's zero-based position in theuniversearray of Hyperliquid's{"type":"meta"}response, matched by exact name to the first part of theinstrumentId(BTC-USDC-PERPgivesBTC). Stay on the main perpdex, and send nodexparameter. 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.bis your side. Sendtrueon aBUYandfalseon aSELL. The maker's order is on the opposite side.sis the size. Copy the base leg to the digit:makerPays.amounton aBUY,makerReceives.amounton aSELL. It sits on the coin'sszDecimalsgrid and equals the quoted size exactly.pis the price. The quote leg ismakerReceives.amounton aBUYandmakerPays.amounton aSELL. Divide it bys, then truncate the result to the decimals Hyperliquid allows (see Hyperliquid's tick and lot size rules).ptimessmust land within one USDC micro-unit of the quote leg. Never derivepfromtakerTotal.pandsare 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.tis the order type, andris your reduce-only flag. Settto{ "limit": { "tif": "Ioc" } }, an immediate-or-cancel (IOC) order. Setrtofalsefor an ordinary order, ortruefor 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.cis your client order ID (cloid). Use 16 random bytes as lowercase0xhex, because uppercase hex changes the hash. Silhouette's checks do not require acloid, but always set one. Without it, an unclear outcome can only end asEXECUTION_UNCONFIRMED, and you have no key to look the order up at Hyperliquid.builderappears only when the quote has a rate. When the quote carriesbuilderFeeTenthBp, addbuilderas{ b, f }.bis Silhouette's builder address for the environment, in lowercase, andfis the quote's rate exactly. WhenbuilderFeeTenthBpis absent, the order carries nobuilderkey: notnull, and not zero. Silhouette compares the value exactly.- 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. - Signing. Sign the action as a Hyperliquid L1 action (see Hyperliquid's signing notes). The typed data is Hyperliquid's L1
Agentmessage (source,connectionId) under the domain nameExchange, version1, chain ID 1337, and a zero verifying contract.connectionIdis thekeccakhash of the msgpack-encoded action, the nonce as eight big-endian bytes, and a0x00byte, joined in that order. - Network and signer. The
sourceis"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 withisTestnetfalse. 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 theSETTLEDone.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_FILLEDandSETTLEMENT_FAILEDend the request. AfterSETTLEMENT_FAILED, raise your builder fee approval at Hyperliquid to the rate first. For either code, submit a new request with a newidempotencyKey, and do not accept again on the failed one.EXECUTION_UNCONFIRMEDmeans a position may exist. Look the order up by yourcloid, 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.
auctionEndsAtis the instant quoting closes, returned when you submit the request. Accept before it.acceptableForMsis how long the maker's quote stays acceptable, counted fromreceivedAt. 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
FAILEDwithEXECUTION_NOT_FILLED.