Skip to main content
The HyperliquidPerpStrategy lets a syndicate run leveraged perpetual positions on Hyperliquid from an on-chain vault. USDC is pulled from the vault, bridged EVM → HyperCore spot via Circle’s CoreDepositWallet (HyperliquidBridge.bridgeUsdcToSpot), and then moved spot → perp margin via L1Write.sendUsdClassTransfer. The proposer drives trading actions (open long, open short, stop-loss, close, plus multi-asset variants) through updateParams. Position state is read live from HyperCore via L1Read.position2() — nothing is cached on-chain.
Not yet available on Robinhood testnet (chain 46630), Sherwood’s current deployment target. This strategy depends on Hyperliquid/HyperEVM precompiles and will come online as Sherwood expands to more chains.
This strategy holds leveraged positions on a centralized-matching venue. A liquidation on HyperCore permanently destroys vault equity up to the deposit amount. Use conservative leverage, set maxPositionSize and maxTradesPerDay at init, and monitor the position directly on Hyperliquid.

Architecture

Lifecycle

PhaseWhat happensWho calls
ExecutePull USDC from vault → bridgeUsdcToSpot (EVM → HC spot) → sendUsdClassTransfer (spot → perp margin)Governor (proposal execution)
ExecutedProposer opens / closes long & short positions, updates stop-losses, trades multiple assets via updateParams. Leverage is set off-chain via the exchange API.Proposer only
SettleForce-close all positions → perp → spot class transfer → spot → EVM bridge → push EVM USDC to vaultGovernor (proposal settlement)
SweepsweepToVault() pushes any latecomer HC USDC to the vault. Callable by anyone; repeatable for partial arrivals.Anyone

Batch Calls

Execute

Settle

After settle(), anyone can call strategy.sweepToVault() once the async USD transfer lands. Funds can only flow to the vault.

InitParams

Init data is an ABI-encoded positional tuple decoded in this order:
On-chain risk caps:
  • leverage must be 1–50
  • Opening a position where sz * limitPx / 1e6 > maxPositionSize reverts with PositionTooLarge
  • Exceeding maxTradesPerDay (counted per UTC day) reverts with MaxTradesExceeded
There is no minReturnAmount floor. It was removed in #255 (S-C6) because it could permanently lock funds on a lossy strategy — sweepToVault() pushes whatever HC returns, with no minimum check.

Proposer Actions (Executed state)

Encoded as updateParams(abi.encode(action, ...)): Single-asset actions use the perpAssetIndex stored at init. Multi-asset actions (6/7/8) carry the assetIndex in calldata, so one clone can trade any whitelisted perp and _settle closes every traded asset.
ActionEncodingDescription
1 — open long(uint8, uint64 limitPx, uint64 sz, uint64 stopLossPx, uint64 stopLossSz)IOC long + GTC stop-loss (reduce-only sell). Stop-loss uses a fixed CLOID so each new one replaces the previous.
2 — close position(uint8, uint64 limitPx, uint64 sz)Reduce-only IOC close.
3 — update stop-loss (long)(uint8, uint64 triggerPx, uint64 sz)Cancel the current stop-loss and place a new GTC reduce-only sell (for longs).
4 — open short(uint8, uint64 limitPx, uint64 sz, uint64 stopLossPx, uint64 stopLossSz)IOC short entry + GTC stop-loss (reduce-only buy to close the short).
5 — update stop-loss (short)(uint8, uint64 triggerPx, uint64 sz)Cancel and place a new GTC reduce-only buy stop for a short position.
6 — open long (multi-asset)(uint8, uint32 assetIndex, uint64 limitPx, uint64 sz, uint64 stopLossPx, uint64 stopLossSz)Same as action 1 but for the assetIndex in calldata.
7 — open short (multi-asset)(uint8, uint32 assetIndex, uint64 limitPx, uint64 sz, uint64 stopLossPx, uint64 stopLossSz)Same as action 4 but for the assetIndex in calldata.
8 — close (multi-asset)(uint8, uint32 assetIndex, bool isBuy, uint64 limitPx, uint64 sz)Reduce-only IOC close on a specific asset and direction (isBuy=true closes a short, false closes a long).
Position direction is not tracked on-chain — the proposer must send the matching stop-loss action (3 for longs, 5 for shorts). Before acting, read HyperCore directly (L1Read.position2). Funds never leave margin on trade actions — settlement / initiateReturn() initiates the transfer back to EVM.

Live NAV

The strategy reports one position of kind HL_PERP pointing at the HyperliquidPerpAdapter. The vault never trusts a self-reported value — the governance-owned PriceRouter prices the position by calling L1Read.accountMarginSummary (the AccountMarginSummary precompile), applying a realizability haircut and an instant-size cap. Lane A is currently disabled (laneAEnabled[HL_PERP] = false) pending a post-audit multisig call. Until enabled, the strategy is Lane B only: deposits and redeems during an active proposal go through the VaultWithdrawalQueue and settle at the frozen per-proposal price. See Deposits & Withdrawals. Transit guards — two windows where HC equity is temporarily unreliable:
  • Inbound (execute → HC credit, ~1–2 blocks): fresh clone has zero HC equity → adapter returns (0, true) → PriceRouter G3 forces Lane B. No code required in the strategy.
  • Outbound (initiateReturn → HC drain): _drainHC() queues HC actions, then returnsInitiated is set to true in the same transaction. The adapter checks this flag and returns (0, false) → Lane B during that window, preventing new depositors from locking into a settling proposal.

Funding the position (bridge mechanics)

Moving USDC from the EVM vault onto HyperCore perp margin is a two-step flow, not a single class transfer:
  1. EVM → HC spotHyperliquidBridge.bridgeUsdcToSpot sends USDC to Circle’s CoreDepositWallet, which credits the clone’s HC spot account post-block. Circle charges a 1-USDC new-account fee on the first deposit per HC address, so the first class transfer can exceed the actually-landed spot and be dropped by HC.
  2. HC spot → perpL1Write.sendUsdClassTransfer(amount, true) moves spot to perp margin. If the first transfer was dropped because of the new-account fee, the proposer calls moveSpotToPerp() one or more blocks later to class-transfer whatever actually landed on spot.
On the way out, _settle() / initiateReturn() reverse this: force-close positions, class-transfer perp → spot, then bridge spot → EVM. The spot → EVM leg consumes HC HYPE for gas — if the clone’s HC HYPE is unfunded, that leg no-ops and recoverHcResiduals() (post-settle) retries it after funding.

Risk Notes

  • Liquidation: HyperCore liquidates positions that breach maintenance margin. The on-chain contract has no view into liquidation state — settlement and sweep still work, but returned USDC may be well below depositAmount.
  • Async settlement: settle() does not immediately return funds. The USD transfer from HyperCore to EVM is asynchronous; sweepToVault() may need to be called multiple times for partial arrivals.
  • No off-chain keeper: All actions are on-chain through precompiles. There is no bot watching price — set stop-losses explicitly (action 3 for longs, 5 for shorts) if you want automated exits.
  • Long and short: The template supports both directions — long (1), short (4), their stop-losses (3/5), close (2), and multi-asset variants (6/7/8). Always pair a position with the correct-direction stop-loss action; a mismatched reduce-only order is a no-op on HyperCore.
  • Funding fees: Perp positions accrue HyperCore funding payments while open; these are reflected in the live margin summary and in the realized USDC at settle.

CLI Usage

FlagDescriptionDefault
--amount <n>USDC collateral to deployrequired
--leverage <n>Leverage multiplier (1–50)10
--asset-index <n>HyperCore perp asset index0 (BTC)
--max-position <amount>Max USD in a single position100000
--max-trades-per-day <n>Daily trading action limit50
After the proposal executes, drive trades with sherwood proposal update-params using the action encodings above.

Addresses (HyperEVM reference)

ContractAddress
HyperliquidPerpStrategy template0xC0fA169fdbBb3638AdE917A5B8A9A87caf90d91e
USDC0xb88339CB7199b77E23DB6E890353E22632Ba630f
SyndicateFactory0xd05Ae0E8bcf13075C29817c805d6Cc14F214393a
SyndicateGovernor0x67AD3D5F3d127Ef923Fd6f67b178633c408D3fd3