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) → treasuryWallet

  • Performance 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 at confirmDeposit (e.g., ±75)

  • PnL drift tolerance: ±<PNL_DRIFT_BPS> bps of payout at confirmWithdrawalWithPnL (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

  • amount: Post‑fee amount credited (net routed to vault).

  • timestamp: Deposit initiation time.

  • nonce: User‑specific deposit nonce.

  • confirmed: Whether finalized.

  • navTimestamp: lastExchangeBalanceTimestamp at quote.

  • navAtQuote: totalAssets() at quote.

  • supplyAtQuote: totalSupply() at quote.

  • pendingTotalAtQuote: Global pending net at quote.

  • reservedAtQuote: reservedForWithdrawals at quote.

5.2 PendingWithdrawal

  • shares: Shares to redeem (locked to the router).

  • timestamp: Initiation time.

  • nonce: User‑specific withdrawal nonce.

  • confirmed: Whether finalized.

5.3 WithdrawalQuote

  • shares: 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

function deposit(uint256 assets, address receiver)
  external override nonReentrant whenNotPaused returns (uint256);

Initiates a deposit:

  • Requires assets > 0, fresh NAV, and within maxDeposit(receiver).

  • Deducts <ENTRY_FEE_BPS> bps entry fee → treasuryWallet; net forwarded to vaultWallet.

  • 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

function confirmDeposit(address user, uint256 amount, bytes32 txHash, uint256 nonce)
  external nonReentrant whenNotPaused onlyRole(ADMIN_ROLE);

Admin finalizes a pending deposit:

  • Per‑user replay: txHash must be unseen for user.

  • 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

function 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

function 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: txHash unused for user; 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 navAtSettlement within ~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 = withdrawalAmount

  • netPnl = calculatedPnl

  • PnL drift check: provided userPnl within ±<PNL_DRIFT_BPS> bps of withdrawalAmount around netPnl.

  • Finalizes:

    • Burns locked shares; updates basis and totals; decrements reservedForWithdrawals & batchUsdcRemaining.

    • Transfers net USDC to user (and perf fee to treasuryWallet if ever enabled).

    • Marks confirmedWithdrawalHash[user][txHash] = true and usedBatchKey[user][batchKey] = true.

  • Emits: UserPnLReported, WithdrawalConfirmed.

6.5 confirmBatchUsdcReceived

function confirmBatchUsdcReceived(
  uint256 quoteTimestamp,
  uint256 batchNonce,
  uint256 usdcReadyNet,
  uint256 navAtSettlement,
  uint256 supplyAtSettlement
) external onlyRole(ADMIN_ROLE) whenNotPaused;

Registers and snapshots a batch:

  • Derives batchKey from batchNonce.

  • Requires usdcReadyNet > 0 and not previously confirmed.

  • Snapshots once: batchNavAtSettlement, batchSupplyAtSettlement (from params or current).

  • Increments reservedForWithdrawals and sets batchUsdcRemaining.

  • Emits: BatchUsdcReceivedConfirmed, BatchSnapshotsSet.

6.6 adminConfirmBridgeReceipt

function 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

function 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); // returns latestExchangeBalance

  • function getVaultLiquidity() public view returns (uint256); // router USDC balance

  • function 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 with UseOffchainConfirmedWithdrawal.

  • mint() disabled with UseOffchainConfirmedDeposit.

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 from vaultWallet/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; mints assets shares 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 − fee

Share Minting (on confirmed deposit)

  • Uses effective NAV (totalAssets − reservedForWithdrawals) consistent with previewDeposit(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 netPnl

PPS 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.

  • quoteTimestamp is accepted in some admin calls for compatibility but batchKey uses only <BATCH_NONCE>.

  • Basis migration on share transfers (EOA↔EOA): the router migrates confirmedUserDeposits and userTotalShares pro‑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: reservedForWithdrawals rises when a batch is confirmed and is consumed as users are settled—protecting exit capacity.

14. Example Sequence (High‑Level)

14.1 Deposit

  1. User approves USDC and calls deposit(assets, receiver) → fee <ENTRY_FEE_BPS> bps to treasuryWallet; net to vaultWallet; PendingDeposit recorded; expected shares returned.

  2. Admin refreshes NAV (updateVaultExchangeBalance) after deposit timestamp.

  3. Admin calls confirmDeposit(user, amountAfterFee, txHash, nonce) → shares minted; events emitted.

14.2 Withdrawal

  1. User calls initiateWithdrawal(shares) → quote snapshotted, shares locked.

  2. Admin funds router and calls confirmBatchUsdcReceived(<BATCH_NONCE>, usdcReadyNet, nav?, supply?) → batch reserved & snapshotted.

  3. 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