Markets
Hyperliquid
How the A2M reference implementation authorizes and executes orders on Hyperliquid.
What it is
Section titled “What it is”Hyperliquid is a perpetual futures DEX with USDC margin. In A2M the venue name is hyperliquid. Instruments are Hyperliquid coin names; the reference adapter is configured for BTC and ETH by default.
Orders are placed on a main account that holds the funds. The adapter signs with a Hyperliquid API wallet (also called an agent wallet) that this main account has approved. An API wallet can trade on behalf of the main account but cannot withdraw or transfer funds. The trading agent never sees the key: only the adapter process holds it, and only FairX can reach the adapter.
How to connect
Section titled “How to connect”- From the main account, approve an API wallet. On testnet that is done at app.hyperliquid-testnet.xyz/API; on mainnet, at app.hyperliquid.xyz/API.
- Configure the venue adapter. It is off by default. Only the adapter process should read the API wallet key; never commit a real key or account address.
HYPERLIQUID_ENABLED=1HYPERLIQUID_NETWORK=testnetHYPERLIQUID_ALLOW_MAINNET=0HYPERLIQUID_ACCOUNT_ADDRESS=0xYourMainAccountAddressHYPERLIQUID_AGENT_PRIVATE_KEY=your-api-wallet-private-keyHYPERLIQUID_INSTRUMENTS=BTC,ETHHYPERLIQUID_NETWORK defaults to testnet. Mainnet requires both HYPERLIQUID_NETWORK=mainnet and HYPERLIQUID_ALLOW_MAINNET=1, so a misconfigured environment cannot trade real funds.
The agent does not connect to Hyperliquid itself. It calls the A2M MCP gateway. Finn and FairX sit between the agent and the adapter.
How it works
Section titled “How it works”sequenceDiagram autonumber actor Agent participant GW as MCP gateway participant Finn participant FX as FairX OMS participant AD as Venue adapter participant HL as Hyperliquid Agent->>GW: a2m_place_order GW->>Finn: POST /v1/authorize Finn-->>GW: signed decision GW->>FX: POST /v1/orders FX->>AD: gRPC Submit AD->>HL: exchange.order HL-->>AD: ack or fills AD-->>FX: result
The steps in more detail:
- The agent calls
a2m_place_orderon the A2M gateway over MCP, authenticated with an OAuth bearer token. - The gateway asks Finn to authorize the intent (
POST /v1/authorize). Finn runs the mandate rules and, onACCEPT, returns a signed, single-use decision with an expiry. - The gateway sends that decision and its signature to FairX (
POST /v1/orders). FairX verifies the signature, checks that the decision is executable, signed forcustodial_fairxenforcement, and not expired, then consumes it so it cannot be used twice. Resubmitting a decision that already became an order returns the existing order and does not send again. - Before sending, FairX checks that the venue is healthy, that the order type and time in force are declared capabilities (it never downgrades one to make an order fit), and that the venue’s current price still sits inside the signed collar. If any of those fail, it voids the decision and refuses; nothing is sent.
- FairX sends the order once to the Python venue adapter over gRPC (
Submit), including a protect price on market orders. It never blindly resends. - The adapter calls Hyperliquid
exchange.order, signed by the API wallet, with acloidderived from theclient_order_id. - FairX records the acknowledgement or fills, then reconciles against venue snapshots and
user_fills. Applied fills update Finn’s reservation ledger; unused buying power is released when the order ends.
Mapping
Section titled “Mapping”| A2M | Hyperliquid |
|---|---|
client_order_id |
cloid: 0x plus the first 16 bytes of sha256(client_order_id) (32 hex characters). The same value is recomputed for cancel and reconcile; the adapter does not need a local mapping table. |
| Market order | Hyperliquid has no native market orders. The order is sent as an IOC limit at the protect price: the signed reference price moved by the signed collar, in basis points (a buy may pay up to reference * (1 + collar_bps / 10000); a sell may receive no less than reference * (1 - collar_bps / 10000)), then rounded to a valid Hyperliquid price on the safe side. A buy is never rounded above the protect price, and a sell is never rounded below it. Market orders must use time_in_force=ioc. |
| Limit price | Sent unchanged. If it does not fit Hyperliquid’s precision rules, the adapter rejects the order. The adapter never alters a price or quantity Finn signed. |
reduce_only |
Passed through as reduce_only. |
time_in_force |
gtc to Gtc, ioc to Ioc, post_only to Alo. fok and day are refused, not downgraded. |
| Account | Must match the configured HYPERLIQUID_ACCOUNT_ADDRESS (comparison is case-insensitive). A mismatch is rejected before anything is sent. |
Supported
Section titled “Supported”- Order types and times in force:
limitwithgtc,iocorpost_only;marketwithioconly;stopandstop_limitas trigger orders (tpsl: sl). Astopcarries the venue’s own TP/SL market slippage tolerance, 10% past the trigger; usestop_limitto set the worst price. - Order classes:
bracketas onenormalTpslbatch (entry, then reduce-only take-profit and stop-loss). The venue has no notional orders or options. Its trailing stop and TWAP are not implemented in the adapter yet;otoandocoare not mapped.replaceuses the venue’s modify. Position-level TP/SL is not an OCO and is only declared as the featureposition_tpsl. - Reduce-only: supported, and passed through to the venue.
- Size precision: size must be a positive multiple of
10^-szDecimalsfor the instrument. - Price precision: a price may have at most 5 significant figures (integers are always allowed) and at most
6 - szDecimalsdecimal places on perpetuals. A signed limit that does not fit is rejected. - Minimum order value: 10 USDC. A reduce-only order may be below this minimum.
- Quote currency: USDC.
- Leverage: an order may carry
leverage; the adapter sets it on the coin first (cross margin, unless the coin is isolated-only) and rejects the order if the venue refuses it. Withoutleverage, the order trades at the account’s current setting, which the adapter does not change. Finn caps that leverage by the rules’max_leverage, reserves notional / leverage, and does not model funding or liquidation.
Reconciliation and failures
Section titled “Reconciliation and failures”These are the states an order can take in FairX, and the transitions the state machine allows. Terminal states are filled, cancelled, rejected, expired and replaced. A bracket whose entry has filled sits in working while its exits rest. An order in unknown changes state only through reconciliation. The full machine, including pending_replace, is in the specification.
stateDiagram-v2 [*] --> new new --> pending_new: sent new --> rejected pending_new --> acked pending_new --> partially_filled pending_new --> filled pending_new --> rejected pending_new --> expired pending_new --> unknown: no definitive venue reply pending_new --> cancelled acked --> partially_filled acked --> filled acked --> cancelled acked --> expired acked --> unknown partially_filled --> partially_filled: further fills partially_filled --> filled partially_filled --> cancelled partially_filled --> expired partially_filled --> unknown unknown --> acked: reconcile unknown --> partially_filled: reconcile unknown --> filled: reconcile unknown --> cancelled: reconcile unknown --> rejected: reconcile unknown --> expired: reconcile filled --> [*] cancelled --> [*] rejected --> [*] expired --> [*]
flowchart TD
Rec["FairX Reconcile"] --> Snap["Adapter snapshot"]
Snap --> Lookup["query_order_by_cloid<br/>same derived cloid"]
Lookup --> Fills["user_fills, fill id hl_ plus trade id"]
Fills --> Dedupe["FairX ignores a fill already applied"]
Dedupe --> Apply["Apply venue status and fills"]
Apply --> Ledger["Outbox to Finn: reservation ledger, mandate usage"]
Apply --> Tca["On filled: FairX records TCA"]
Lookup --> Missing{"Two definitive snapshots<br/>do not contain the order?"}
Missing -->|yes, and never acknowledged| Reject["rejected, not found at the venue"]
Missing -->|not yet| Watch["Keep watching"]
Cancel["Cancel by cloid"] --> Confirm["Confirm against a snapshot"]
Confirm -->|cancel visible; fills applied first| Release["Release the reservation"]
Confirm -->|not yet visible| Pending["A2M_CANCEL_PENDING; reconcile settles it"]
- Acknowledgement timeout. If the submit deadline is missed, or the venue reply is otherwise ambiguous, the order moves to
unknown. FairX never resends. Reconciliation looks the order up withquery_order_by_cloid(the same derivedcloid) and applies the venue’s status and fills. An order that was never acknowledged, and that two definitive snapshots in a row do not contain (a complete snapshot, or one that lists the id as missing), is eventually rejected as not found at the venue. - Cancel. The adapter cancels by
cloid. FairX then confirms against a snapshot before releasing the reservation, so a fill that raced the cancel is applied first. If the snapshot does not yet show the cancel, the caller receivesA2M_CANCEL_PENDINGand reconcile settles it. - Fills. Each fill is recorded with id
hl_<tid>(Hyperliquid’s trade id) and is ignored if that id has already been applied. Applied fills are delivered to Finn, which updates the reservation ledger (the mandate usage the position limit is checked against). When an order reachesfilled, FairX records TCA against the arrival price Finn signed. - Expired decision. A decision whose expiry has passed is refused with
A2M_DECISION_EXPIREDand voided. Nothing is sent. - Venue down. If the venue is unhealthy, or it cannot report its capabilities, FairX fails closed with
A2M_VENUE_DOWNand does not send. A missing or stale venue price is also a refusal (A2M_STALE_PRICE).
Status
Section titled “Status”- Testnet: verified with real orders and fills, including stop triggers, brackets and replaces.
- Mainnet: not yet. The adapter will trade mainnet only when both
HYPERLIQUID_NETWORK=mainnetandHYPERLIQUID_ALLOW_MAINNET=1are set.