HyperliquidVaultRouter Contract
1. Introduction
The HyperliquidVaultRouter is the bridge between user USDC deposits and the Devine OS omnichain vault system on Arbitrum. It exposes:
Deposit flow with entry fee, pending state, and admin confirmation to mint ERC‑4626 shares.
Withdrawal flow with quote locking, batch‑based off‑chain settlement, on‑chain PnL computation, and admin finalization.
Batch liquidity management that reserves and tracks funds for redemptions.
Role‑based access control for sensitive operations (NAV updates, confirmations, batch ops).
Seamless UI/UX for retail users and institutional dashboards/APIs for teams—same contract guarantees.
2. Constants & Parameters (Illustrative)
Entry fee (deposit):
<ENTRY_FEE_BPS>bps (e.g., 75) →treasuryWalletPerformance fee (withdrawal):
<PERF_FEE_BPS>bps on positive PnL (e.g., 0)NAV freshness gate:
MAX_NAV_AGE = <MAX_NAV_AGE_MINUTES>minutes (e.g., 180; configurable<MIN_NAV_AGE_MINUTES>–<MAX_NAV_AGE_MINUTES>e.g., 5–240)Deposit PPS drift envelope:
±<PPS_DRIFT_BPS>bps atconfirmDeposit(e.g., ±75)PnL drift tolerance:
±<PNL_DRIFT_BPS>bps of payout atconfirmWithdrawalWithPnL(e.g., ±50)Deposit‑basis increase tolerance (post‑quote):
+<BASIS_INCREASE_TOL_PCT>% of basis + <BASIS_INCREASE_TOL_ABS>(USDC, 6dp; e.g., +1.20% and 1,000,000)Batch key:
batchKey = keccak256(abi.encodePacked(<BATCH_NONCE>))(per‑batch; one‑time user consumption)
3. Immutables
Token interface: ERC‑4626 upgradeable; symbol/name: e.g.,
<VAULT_SYMBOL>/<VAULT_NAME>; decimals:<VAULT_DECIMALS>(e.g., 6)Underlying asset constraint: must be USDC‑style (6 decimals)
(No Solidity immutable variables; contract is upgradeable.)
4. Overview
Users deposit USDC and, after admin confirmation with fresh NAV, receive <VAULT_SYMBOL> shares. Withdrawals are user‑initiated (capturing a quote and locking shares), batch liquidity is prepared and snapshotted by admin, and finalization computes pro‑rata basis reduction & PnL with on‑chain checks (drift, NAV match, liquidity). Standard ERC‑4626 direct withdraw/redeem are disabled to bind exits to batch solvency and proofs.
4.1 Deposit
User calls deposit; router takes <ENTRY_FEE_BPS> bps fee, routes net to vaultWallet, records a PendingDeposit; shares mint only after admin calls confirmDeposit with a fresh NAV check & PPS drift envelope.
4.2 Withdrawal
User calls initiateWithdrawal to snapshot a WithdrawalQuote and lock shares; admin funds & snapshots a batch, then calls confirmWithdrawalWithPnL to burn shares, apply PnL logic (<PERF_FEE_BPS> bps on positive PnL), and transfer net USDC.
4.3 Batch Settlement
Batches are keyed by <BATCH_NONCE>; liquidity must be confirmed and snapshotted before user confirmations consume it.
5. Structs
5.1 PendingDeposit
PendingDepositamount: Post‑fee amount credited (net routed to vault).timestamp: Deposit initiation time.nonce: User‑specific deposit nonce.confirmed: Whether finalized.navTimestamp:lastExchangeBalanceTimestampat quote.navAtQuote:totalAssets()at quote.supplyAtQuote:totalSupply()at quote.pendingTotalAtQuote: Global pending net at quote.reservedAtQuote:reservedForWithdrawalsat quote.
5.2 PendingWithdrawal
PendingWithdrawalshares: Shares to redeem (locked to the router).timestamp: Initiation time.nonce: User‑specific withdrawal nonce.confirmed: Whether finalized.
5.3 WithdrawalQuote
WithdrawalQuoteshares: Shares being withdrawn.navAtQuote: Vault NAV snapshot (totalAssets()).supplyAtQuote: Total share supply at quote.userDepositAtQuote: User’s confirmed deposit basis.userSharesAtQuote: User’s share balance at quote.timestamp: Quote creation time.
6. Functions
6.1 deposit
depositfunction deposit(uint256 assets, address receiver)
external override nonReentrant whenNotPaused returns (uint256);Initiates a deposit:
Requires
assets > 0, fresh NAV, and withinmaxDeposit(receiver).Deducts
<ENTRY_FEE_BPS>bps entry fee →treasuryWallet; net forwarded tovaultWallet.Records
PendingDeposit(nonce + NAV/supply/pending/reserve snapshots).Does not mint shares; returns
previewDeposit(net)(expected shares using effective NAV =NAV − reservedForWithdrawals).Emits:
DepositInitiated,VaultOutflowToPlatform,FeeTransferred.
6.2 confirmDeposit
confirmDepositfunction confirmDeposit(address user, uint256 amount, bytes32 txHash, uint256 nonce)
external nonReentrant whenNotPaused onlyRole(ADMIN_ROLE);Admin finalizes a pending deposit:
Per‑user replay:
txHashmust be unseen foruser.NAV freshness & ordering:
lastExchangeBalanceTimestamp > deposit.timestamp.PPS drift envelope:
±<PPS_DRIFT_BPS>bps (reserve‑ and pending‑adjusted).Decrements
pendingDepositsTotal; mints shares exactly per quote (previewDeposit(net)logic).Updates
confirmedUserDeposits[user],totalConfirmedDeposits,userTotalShares[user].Emits:
DepositConfirmed.
6.3 initiateWithdrawal
initiateWithdrawalfunction initiateWithdrawal(uint256 shares)
external nonReentrant whenNotPaused returns (uint256 nonce);User locks in a withdrawal quote:
Requires sufficient share balance and fresh NAV.
Snapshots
WithdrawalQuote(NAV, supply, user basis/shares).Locks shares by transferring to the router (internally permitted) and records
PendingWithdrawal.Returns a user‑specific
nonce.Emits:
WithdrawalInitiated.
6.4 confirmWithdrawalWithPnL
confirmWithdrawalWithPnLfunction confirmWithdrawalWithPnL(
address user,
uint256 shares,
bytes32 txHash,
uint256 nonce,
int256 userPnl,
uint256 batchNonce,
uint256 navAtSettlement
) external nonReentrant onlyRole(ADMIN_ROLE) whenNotPaused;Admin finalizes a withdrawal:
Batch key:
batchKey = keccak256(abi.encodePacked(batchNonce))must be snapshotted and funded.Idempotency:
txHashunused foruser;usedBatchKey[user][batchKey] == false.Pending withdrawal must match (
shares,nonce); shares must be locked.Liquidity checks:
batchUsdcRemaining[batchKey] > 0, router USDC ≥withdrawalAmount.Basis growth guard:
confirmedUserDeposits[user] ≤ basisAtQuote + <BASIS_INCREASE_TOL_PCT>% + <BASIS_INCREASE_TOL_ABS>.NAV match: provided
navAtSettlementwithin ~1 ppm of stored batch NAV (min 1).Computes:
withdrawalAmount = floor(shares * batchNavAtSettlement / batchSupplyAtSettlement)userDepositReduction = floor(shares * quote.userDepositAtQuote / quote.userSharesAtQuote)calculatedPnl = int(withdrawalAmount) − int(userDepositReduction)
Performance fee:
<PERF_FEE_BPS>bps on positive PnL (e.g., 0 for DG‑style)netWithdrawalAmount = withdrawalAmountnetPnl = calculatedPnlPnL drift check: provided
userPnlwithin±<PNL_DRIFT_BPS>bps ofwithdrawalAmountaroundnetPnl.Finalizes:
Burns locked shares; updates basis and totals; decrements
reservedForWithdrawals&batchUsdcRemaining.Transfers net USDC to
user(and perf fee totreasuryWalletif ever enabled).Marks
confirmedWithdrawalHash[user][txHash] = trueandusedBatchKey[user][batchKey] = true.
Emits:
UserPnLReported,WithdrawalConfirmed.
6.5 confirmBatchUsdcReceived
confirmBatchUsdcReceivedfunction confirmBatchUsdcReceived(
uint256 quoteTimestamp,
uint256 batchNonce,
uint256 usdcReadyNet,
uint256 navAtSettlement,
uint256 supplyAtSettlement
) external onlyRole(ADMIN_ROLE) whenNotPaused;Registers and snapshots a batch:
Derives
batchKeyfrombatchNonce.Requires
usdcReadyNet > 0and not previously confirmed.Snapshots once:
batchNavAtSettlement,batchSupplyAtSettlement(from params or current).Increments
reservedForWithdrawalsand setsbatchUsdcRemaining.Emits:
BatchUsdcReceivedConfirmed,BatchSnapshotsSet.
6.6 adminConfirmBridgeReceipt
adminConfirmBridgeReceiptfunction adminConfirmBridgeReceipt(
uint256 quoteTimestamp,
uint256 batchNonce,
uint256 amount,
bytes32 hyperliquidTxHash,
string calldata hyperliquidAddress
) external onlyRole(ADMIN_ROLE) nonReentrant;Logs an off‑chain bridge receipt tied to a batchKey.
Emits:
BridgeWithdrawalReceipt.
6.7 updateVaultExchangeBalance
updateVaultExchangeBalancefunction updateVaultExchangeBalance(uint256 newBalance)
external onlyRole(ADMIN_ROLE) nonReentrant;Updates vault NAV (latestExchangeBalance) and timestamp.
Emits:
ExchangeBalanceUpdated.
7. Utility / View Functions
function totalAssets() public view override returns (uint256);// returnslatestExchangeBalancefunction getVaultLiquidity() public view returns (uint256);// router USDC balancefunction getNextExpectedNonce(address user) public view returns (uint256);function scaleAmount(uint256 amount, uint8 fromDecimals, uint8 toDecimals) public pure returns (uint256);
Direct ERC‑4626 exits disabled (binds to batches):
withdraw/redeem/preview*revert withUseOffchainConfirmedWithdrawal.mint()disabled withUseOffchainConfirmedDeposit.
Direct ERC‑4626 exits are disabled to ensure batch‑bound solvency and proofs.
8. Admin Emergency / Force Utilities
adminForceConfirmDeposit(user, nonce, txHash): Force‑confirms pending deposit (bypasses NAV ordering/drift); mints per quote; updates basis.adminCancelPendingDeposit(user, nonce): Cancels pending; reconstructs gross (pulls net+fee back fromvaultWallet/treasuryWallet) and refunds user; consumes pending.adminForceConfirmWithdrawal(user, shares, nonce, txHash, userPnl, netAmount): Force‑finalizes withdrawal with provided PnL & net (solvency checks on router balance).adminCancelPendingWithdrawal(user, nonce): Cancels pending withdrawal; unlocks shares back to user; no funds moved.adminForceConfirmBatchUsdc(quoteTimestamp, batchNonce, usdcReadyNet): Force‑sets batch USDC and snapshots current NAV/supply.seedVault(uint256 assets): One‑time init; mintsassetsshares to caller and sets NAV/timestamp.
9. Access Control Management
function grantAdminRole(address account) external onlyRole(ADMIN_ROLE);function revokeAdminRole(address account) external onlyRole(ADMIN_ROLE);function setVaultWallet(address newWallet) external onlyRole(ADMIN_ROLE);// set to<VAULT_WALLET>function setTreasuryWallet(address newWallet) external onlyRole(ADMIN_ROLE);// set to<TREASURY_WALLET>function setMaxNavAge(uint256 secs) external onlyRole(ADMIN_ROLE);// set to<MAX_NAV_AGE_SECONDS>(5m–4h)function pause() external onlyRole(ADMIN_ROLE);function unpause() external onlyRole(ADMIN_ROLE);
Manages ADMIN_ROLE, updates wallets/parameters, and pause state.
10. Events
DepositInitiated(address indexed user, uint256 amount, uint256 nonce)VaultOutflowToPlatform(address indexed user, uint256 amount, address platformWallet)FeeTransferred(address indexed user, uint256 fee, address treasuryWallet)DepositConfirmed(address indexed user, uint256 amount, uint256 shares, bytes32 txHash)WithdrawalInitiated(address indexed user, uint256 shares, uint256 nonce)WithdrawalConfirmed(address indexed user, uint256 amount, uint256 shares, bytes32 txHash, uint256 userDepositReduction)UserPnLReported(address indexed user, int256 userPnl)BatchUsdcReceivedConfirmed(bytes32 indexed batchKey, uint256 usdcReadyNet)BatchSnapshotsSet(bytes32 indexed batchKey, uint256 nav, uint256 supply)BridgeWithdrawalReceipt(bytes32 indexed batchKey, uint256 amount, bytes32 hyperliquidTxHash, string hyperliquidAddress)ExchangeBalanceUpdated(uint256 newBalance, uint256 timestamp)MaxNavAgeUpdated(uint256)•VaultWalletUpdated(...)•TreasuryWalletUpdated(...)PendingDepositCancelled(...)•BasisMigrated(address from, address to, uint256 shares, uint256 basisMoved, uint256 mintedMoved)
11. Errors
Common: ZeroDeposit, ZeroNAV, NAVStale, AmountMismatch, AlreadyConfirmed, DepositTooSmall, NotEnoughShares, ShareMismatch
Deposits: MustUpdateNAVAfterDeposit, PPSDrift
Withdrawals/Batches: BatchNotSnapshotted, TxAlreadyConfirmedForUser, BatchAlreadyConsumed, ReserveUnderflow, InsufficientBatchLiquidity, InsufficientOnchainLiquidity, SharesNotLocked, DepositBasisIncreasedPostQuote, ZeroBatch, BadSnapshot, BatchSnapshotsNotSet, NavMismatchWithBatchSnapshot, PnLDrift, FeeExceedsWithdrawal
Admin/System: UnsupportedDecimals, OutOfBounds, ZeroAddress, InvalidAmount, BatchNotConfirmed, NothingToCancel, UnderlyingProtected, UseOffchainConfirmedWithdrawal, UseOffchainConfirmedDeposit, NoETH, NoDirectCalls, NoDirectShareDeposits
12. Calculations
Entry Fee
fee = assets * (<ENTRY_FEE_BPS> / 10_000)
depositAfterFee = assets − feeShare Minting (on confirmed deposit)
Uses effective NAV (
totalAssets − reservedForWithdrawals) consistent withpreviewDeposit(depositAfterFee).
Withdrawal Amount
withdrawalAmount = floor(shares * batchNavAtSettlement / batchSupplyAtSettlement)User Deposit Reduction (basis)
userDepositReduction = floor(shares * quote.userDepositAtQuote / quote.userSharesAtQuote)PnL
calculatedPnl = int(withdrawalAmount) − int(userDepositReduction)Performance Fee
performanceFee = <PERF_FEE_BPS> bps of max(calculatedPnl, 0)PnL Drift Tolerance
±<PNL_DRIFT_BPS> bps of withdrawalAmount around netPnlPPS Drift (deposit confirm)
±<PPS_DRIFT_BPS> bps envelope vs. quote (reserve‑ & pending‑adjusted)Batch Key Derivation
batchKey = keccak256(abi.encodePacked(<BATCH_NONCE>))13. Notes & Observations
Direct ERC‑4626 exits are disabled: all redemptions are batch‑bound with on‑chain solvency & replay protection.
quoteTimestampis accepted in some admin calls for compatibility butbatchKeyuses only<BATCH_NONCE>.Basis migration on share transfers (EOA↔EOA): the router migrates
confirmedUserDepositsanduserTotalSharespro‑rata to preserve user‑level accounting; transfers to the router are blocked except during the lock step of withdrawals.NAV freshness is strictly enforced; deposit confirmations must follow a later NAV (
lastExchangeBalanceTimestamp > deposit.timestamp).Reserved liquidity:
reservedForWithdrawalsrises when a batch is confirmed and is consumed as users are settled—protecting exit capacity.
14. Example Sequence (High‑Level)
14.1 Deposit
User approves USDC and calls
deposit(assets, receiver)→ fee<ENTRY_FEE_BPS>bps totreasuryWallet; net tovaultWallet;PendingDepositrecorded; expected shares returned.Admin refreshes NAV (
updateVaultExchangeBalance) after deposit timestamp.Admin calls
confirmDeposit(user, amountAfterFee, txHash, nonce)→ shares minted; events emitted.
14.2 Withdrawal
User calls
initiateWithdrawal(shares)→ quote snapshotted, shares locked.Admin funds router and calls
confirmBatchUsdcReceived(<BATCH_NONCE>, usdcReadyNet, nav?, supply?)→ batch reserved & snapshotted.Admin calls
confirmWithdrawalWithPnL(user, shares, txHash, nonce, userPnl, <BATCH_NONCE>, navAtSettlement)→ burn shares, apply PnL checks (<PERF_FEE_BPS>bps), transfer net USDC; events emitted.
Last updated