Funding Rate Arbitrage Vault
Overview
A step-by-step guide to setting up, configuring, and operating the Markov Funding Rate Arbitrage (mkFRA) vault on Enzyme, including deposit processing and delta-neutral trade execution.
The strategy runs a delta-neutral position: a leveraged long leg on Aave (Ethereum mainnet) hedged by an equal short leg on Hyperliquid, capturing funding-rate spread while keeping net ETH exposure close to zero.
Users can deposit using the following link: Funding Rate Arbitrage Vault.
1. Address Setup & Management
To initiate the vault's operations, three distinct address groups must be set up: a Safe multisig on Ethereum mainnet, a Safe multisig on HyperEVM, and a Hyperliquid Perps executor.
Address requirements
Vault Owner Multisig (Ethereum): the address that owns the vault.
Vault Administrator Multisig (Ethereum): performs administration operations such as updating NAV and accepting redemptions/withdrawals.
Trading Multisig (Ethereum and HyperEVM mainnet): the address where the long leg of the Aave loop is executed (on Ethereum mainnet) and from which bridging to HyperEVM mainnet happens.
Hyperliquid Perps Executor: an address dedicated to executing trades on Hyperliquid.
Multisig architecture
Initialization & security
Deployment: deploy both Safe instances with owner permissions assigned to the three designated signers.
Permissions: 3/3 for the Owner Multisig; 2/3 for the others.
Gas funding & maintenance
Operations require gas funding:
Ethereum / HyperEVM: native tokens (
$ETHand$HYPE) are required for transaction fees related to deposits, withdrawals, and bridging.Executor address: the Hyperliquid executor requires native gas (
$HYPE) for trade execution and management.
Official addresses
To adopt a naming standard that facilitates signing, the setup references the following canonical addresses:
Address 1
Owner Multisig
0xaf76Ea156FC9a2290ef6D1E1C784CCB2b4bdf9EC
Address 2
Administrator Multisig
0x1cF751685d24C096FF99855797FE3E1f13B87A8c
Address 3
Trading Multisig
0xbCA3078239Ec3b8553A424d6531792c09B9102F7
Address 4
HyperCore Executor
0x192Cd74D7E740bbE35103BdbF3A5DC5f8A864489
Each multisig is controlled by its three designated signers. Individual signer wallets are not published.
2. Configuration & Initial Deposit
After the Enzyme team creates the vault and transfers ownership to the owner multisig, the vault must be finished being configured.
This involves:
Configuring the Owner multisig also as vault administrator.
Setting up the allowlist of authorized investors using the
addDepositControllerToInternalAllowlist(address)method.
Once the first whitelisted address is configured, the initial deposit is made through the Enzyme Vault UI, similar to any other DeFi protocol interaction.
Processing the first deposit
From the administrative side, the vault admin must process every pending request. Before accepting, the Net Asset Value (NAV) of the vault should always be updated. Afterwards, the deposit is finalized by calling executeDepositRequests([pending_deposit_index]).
Confirmation of deployment
Once the vault is deployed and the first deposit is accepted, verify that the shares received by the depositor address match the number of outstanding shares in the vault, and that the subscription price also matches the vault's NAV.
3. Execute a Trade
Pre-trade setup
With funds already in the vault, the following steps set up a trade:
Transfer USDC to the trading Safe (an address outside of the vault) using
withdrawAssetTo(tradingSafe, usdc_amount).From the trading Safe, take half of the received USDC and bridge it to HyperEVM through Circle CCTP. To get the transaction batch, make a request to the proposer service.
After ~15 minutes, once the USDC lands in the HyperEVM multisig address, transfer the USDC to the HyperCore executor address on HyperEVM. Once it lands, bridge that USDC to HyperCore using the Hyperliquid UI.
To confirm success, verify that the same USDC amount sits idle on the mainnet multisig and as margin in the HyperCore unified account.
Execution
To execute the trade as planned:
Make a request to the proposer for opening a trade with the intended amount.
Take the JSON batch and drop it in the SAFE UI for the transaction to be proposed.
Confirm the orchestrator is listening for transactions and that the price spread is positive.
Sign the transaction and execute the Ethereum mainnet leg on Aave.
Confirm that the short leg was also opened on Hyperliquid.
Confirm that the position is delta-neutral and the fees paid were correctly accounted for.
The end result should be a position very close to delta-neutral (minus the trading fees paid).
4. Vault Operations
This section details further vault flows: serving a redemption request, updating the NAV, accepting multiple deposits, and rebalancing margin accounts between the Aave and Hyperliquid legs.
Update the NAV
Before any deposit or redemption request is processed, or after any trade is taken, the AuM of the vault needs to be updated (along with daily updates). The indexer services provide the AuM of the strategy outside of the vault. Once that value is known, a transaction batch is built on the Safe UI to submit the new AuM value.
Tx details
Contract: ValuationHandler
0x7a5b5fde46ff2fd8dec412a38dafdce4822ccb25Method:
updateShareValue(int256)Off-vault AuM in 18 decimals (
$100 → 100e18)
Accept deposit requests
Once the NAV is updated, the admin can accept one or multiple pending requests at once. As long as there is at least one pending request, the method can be extended to multiple indexes: executeDepositRequests([id1, id2]).
Tx details
Contract: DepositHandler
0xd1e6d37d90ef493dc29937626e88050a3654a950Method:
executeDepositRequests(uint256[] ids)
{% hint style="info" %} Finding IDs is possible by scanning getDepositRequest(id) up to getDepositLastId(). {% endhint %}
Serve redemption requests
Before serving redemption requests, besides updating the NAV, there should be enough USDC on the vault address to serve the redemptions being approved. Anytime before a redemption request is approved and executed, the vault NAV should always be updated using the method described above.
Tx details
Contract: RedeemHandler
0x2c7b9f6f77de567eb3ab837b894fa599e6a7daaeMethod:
executeRedeemRequests(uint256[] ids)
5. Open a Trade
Opening a trade should always follow the pre-trade setup and execution steps described above. Never open trades below 25 USDC (Hyperliquid's minimum trade amount is 10 USDC), and always update the NAV afterwards.
Opening a trade has two transactions, but only one needs to be signed at the multisig level (the Ethereum mainnet leg), as the HyperCore one is executed automatically using the API Wallet.
Contracts involved (Ethereum mainnet)
Aave V3 Pool
0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2
aEthWETH (collateral receipt)
0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8
Uniswap SwapRouter02
0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
USDC
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
WETH
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Safe MultiSend (delegatecall)
0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761
Chainlink ETH/USD feed
0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
Swaps use the USDC/WETH 0.05% pool (fee = 500).
Open tx flow (iterative loop, no flashloan)
USDC.approve(router, total_usdc)— initial amount plus all planned borrows.WETH.approve(pool, total_weth)— sum of all supply amounts.Seed step:
SwapRouter02.exactInputSingle(USDC → WETH, fee 500), thenPool.supply(WETH, amount, onBehalfOf = Safe, referralCode 0).Per loop step until ~2x leverage:
Pool.borrow(USDC, amount, rateMode 2, 0, Safe)SwapRouter02.exactInputSingle(USDC → WETH)Pool.supply(WETH, amount, Safe, 0)
Each supply amount is set slightly below the swap's expected output, so a small WETH dust remainder is left in the Safe.
6. Close a Trade
There are two situations when closing: closing a portion of the position, or closing the entire position.
For a partial close, if the trade is small enough, request a close trade from the proposer. It provides the JSON transaction batch to withdraw wETH from Aave and swap to USDC, while the orchestrator reduces the short position by an equal amount. If the balance is small enough, a rebalance can be produced to balance the margin levels after the trade. If the amount is too big, this needs to be done in several steps while rebalancing the liquidity.
For a full close, perform the same multiple-batch approach until the remainder of the position reaches a size small enough to execute the inverse flow of opening a position. When this happens, the orchestrator engine also closes the entire short position so the delta exposure to ETH becomes 0.
Close tx flow (iterative deleverage)
WETH.approve(router, total)andUSDC.approve(pool, total).Per deleverage step:
Pool.withdraw(WETH, amount, to = Safe)SwapRouter02.exactInputSingle(WETH → USDC)Pool.repay(USDC, amount, rateMode 2, Safe)
Final step:
Pool.withdraw(WETH, MAX_UINT256)sweeps all remaining aWETH, then a conservative swap WETH → USDC.
Result should be roughly (collateral − debt) USDC in the Safe, and the position fully closed.
7. Rebalance Position
There are two rebalancing behaviours: from Aave to Hyperliquid, and from Hyperliquid to Aave.
Aave → Hyperliquid: borrow more USDC in the intended amount, bridge it to HyperEVM using Circle CCTP, and from the HyperEVM multisig address make the path to HyperCore to deposit this USDC as margin.
Hyperliquid → Aave: do the opposite. There is an important difference: the Circle CCTP relay service only automatically mints the USDC when the bridge happens from Ethereum mainnet to HyperEVM. When bridging the opposite path, once the Iris API from Circle gives permission, the USDC must be minted to the multisig on Ethereum mainnet.
For both rebalance behaviours, the request can be made to the proposer service, which provides the JSON files for the required transactions.
8. Vault Example Trade
A worked example of a full delta-neutral open.
1. NAV update + remaining deposits + forward
Admin Safe nonce 7 (mainnet) — Tx Hash: 0x23ea28b93c6edacc2dcdb6092c81c3fee49848847a91a8b693eddc3952e49a3c
One MultiSend:
updateShareValue(1000.738599…e18)— refreshed untracked NAV ≈ $1,000.74.executeDepositRequests([4, 5]).vault.withdrawAssetTo(USDC, tradingSafe, 50_000_000000)— 50,000 USDC forwarded from the vault to the trading Safe.
2. Bridge 25k to HyperEVM (CCTP)
Trading Safe nonce 2 (mainnet) — Tx Hash: 0xafcb9934dfd49c3902648e985cfd5eddfce1b50d72186ca6087fb25f902fe8c3
USDC.approve(TokenMessengerV2 0x28b5…cf5d, 25_000_000000)depositForBurn(25_000_000000, domain 19, mintRecipient = trading Safe, minFinality 2000)— standard-finality burn.
{% hint style="info" %} The mint on HyperEVM is auto-relayed (no manual receiveMessage). {% endhint %}
3. Fund the HL executor (two-phase, HyperEVM)
Trading Safe nonce 3 (HyperEVM) — Tx Hash: 0xa456386cdcf787577166950474fc60821945f2fce370bc21d7b7052c48de8b35
USDC.approve(CoreDepositWallet 0x6B9E…0A24, 25_000_000000)CoreDepositWallet.deposit(25_000_000000, dex = spot)— EVM → Core spot, lands on the Safe's Core spot balance.
Trading Safe nonce 4 (HyperEVM) — Tx Hash: 0x3e1f5e07f0f43107790a15f3635d8d9d01582383b9aa9a4f24f3ffb312ef5853
CoreWriter.sendRawAction— spotSend (action 6): 25,000 USDC (2.5e12 Core wei, 8-dec) from the Safe's Core spot to the executor0x192Cd…4489. Spot USDC collateralizes the perp (unified account).
4. Aave 2x open long
Trading Safe nonce 3 (mainnet) — Tx Hash: 0x517c61b61a846da7d57578e42d91224882d8187f4632dfb7071757643d6cd0b8
One MultiSend on Aave Pool 0x8787…A4E2 / SwapRouter02 0x68b3…Fc45:
USDC.approve(router): 50,000 USDCWETH.approve(pool): 26.953219 WETHexactInputSingle(USDC → WETH)seed (in 25,000, minOut 13.271382)Pool.supply(WETH): 13.476610Pool.borrow(USDC, variable): 17,500exactInputSingle(USDC → WETH): in 17,500, minOut 9.289967Pool.supply(WETH): 9.433627Pool.borrow(USDC, variable): 7,500exactInputSingle(USDC → WETH): in 7,500, minOut 3.981415Pool.supply(WETH): 4.042983
Result: 26.953219 WETH supplied, 25,000 USDC debt — 2x long on 25k equity. Swap output above the conservative supply amounts (~0.71 WETH) stays in the Safe as idle WETH.
5. HL short — the mirror trade
4s after the Aave batch — orchestrator-armed hedge, executor 0x192Cd…4489:
IOC sell, oid 497774304091: 7 partial fills, 26.9428 ETH @ 48,690 USDC.
Fill hash: 0xfbb7cfe1953f05d5fd3104402293a002058300c7303224a89f807b345432dfc0
Short after: 27.5119 ETH = exactly the Aave principal (0.5691 pre-existing pilot + 26.9428).
6. Netting the small delta
{% hint style="info" %} Whenever execution is not perfect, a mechanism is in place to always check the delta not hedged (or over-hedged) and adjust it. {% endhint %}
One-shot IOC to net a small idle-WETH residual (the ~0.71 WETH swap surplus stranded in the Safe at step 5, unhedged by the principal-only orchestrator target):
IOC sell, oid 497780587330: 0.7097 ETH @ 1,285 USDC.
Fill hash: 0xd19daae75ff02c06d317044022a58702054c00ccfaf34ad87566563a1ef405f1
Final short: 28.2216 ETH = principal 27.512 + residual 0.710.
End state
Aave leg: 26.953 aWETH collateral / 25,000 USDC variable debt (2x).
HL leg: 28.2216 ETH short, ~25k USDC margin (~2x at entry).
Safe: ~0.71 idle WETH (hedged by the extra short), USDC dust.
Last updated