Lagoon vault on HyperEVM with Hypercore deposits

Here is a Python example how to deploy a Lagoon vault on HyperEVM and deposit USDC into a Hypercore vault. This is a low-level code example that shows every step in the smart contract deployment and deposit/withdrawal process.

The script will deploy the vault, the guard smart contract, deposit and withdraw from the deployed Lagoon vault to native Hypercore vaults:

  1. Deploy a Lagoon vault with Hypercore guard integration

  2. Whitelist the target Hypercore vault and CoreWriter contracts

  3. Fund the Safe with USDC (deposit amount + activation overhead)

  4. Activate the Safe’s HyperCore account via depositFor

  5. Bridge USDC from HyperEVM to HyperCore spot (phase 1)

  6. Wait for EVM escrow to clear

  7. Move USDC from spot to perp and deposit into vault (phase 2)

  8. Verify the deposit landed on HyperCore

  9. Withdraw from the vault (if ACTION=both or ACTION=withdraw)

  10. Print a summary of all transactions and gas costs

Prerequisites

You need:

  • A HyperEVM wallet funded with HYPE (gas) and USDC (see amounts below)

  • Environment variables configured (see below)

Required funds

Your wallet must have the following minimum balances on HyperEVM:

HYPE (~0.1 HYPE recommended)

Used for gas fees. The tutorial performs multiple transactions:

  • Vault deployment (big blocks): ~0.02–0.05 HYPE

  • Guard configuration: ~0.005 HYPE

  • USDC transfer to Safe: ~0.001 HYPE

  • Account activation: ~0.001 HYPE

  • Deposit phases: ~0.002 HYPE

  • Withdrawal: ~0.002 HYPE

Total gas costs vary with network congestion. Having 0.1 HYPE provides a comfortable buffer. Big block transactions take ~1 minute to confirm.

USDC (~$7 minimum for deposit)

Broken down as:

  • $5 USDC for the vault deposit (minimum Hypercore vault deposit)

  • $2 USDC for HyperCore account activation ($1 creation fee + $1 reaches spot)

You can modify USDC_AMOUNT to deposit larger amounts.

Account funding for HyperEVM testnet

Tip

We recommend using HyperEVM mainnet for testing, because testnet EVM bridging does not seem to work, and factory contracts are not available on the testnet.

Funding a HyperEVM testnet account requires several steps because there is no direct faucet:

  1. Create a new private key and set HYPERCORE_WRITER_TEST_PRIVATE_KEY

  2. Move ~$2 worth of ETH on Arbitrum to that address

  3. Move ~$5 worth of USDC on Arbitrum to that address

  4. Sign in to app.hyperliquid.xyz with the new account

  5. Deposit $5 USDC (minimum)

  6. Now you have an account on Hyperliquid mainnet

  7. Visit app.hyperliquid-testnet.xyz/drip and claim

  8. Now you have 1,000 USDC on the Hypercore testnet

  9. Buy 1 HYPE with the mock USDC (set max slippage to 99%, testnet orderbook is illiquid)

  10. Visit Testnet portfolio — click EVM <-> CORE

  11. Move 100 USDC to HyperEVM testnet

  12. Move 0.01 HYPE to HyperEVM testnet

  13. Check HyperEVM testnet balance on EVM <-> CORE dialog (there is no working HyperEVM testnet explorer)

Environment variables

Refer to the script content for the full list of environment variables.

Running the script

Simulate on Anvil fork (no real funds needed):

# Simulate — deploys mock contracts on Anvil fork
python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

# Explicit simulate flag
SIMULATE=true python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

Mainnet deployment (recommended for testing):

# Set your private key (must have HYPE + USDC on HyperEVM)
export HYPERCORE_WRITER_TEST_PRIVATE_KEY="0x..."
export JSON_RPC_HYPERLIQUID="https://rpc.hyperliquid.xyz/evm"

# Deploy vault and deposit 5 USDC into HLP vault
NETWORK=mainnet ACTION=deposit python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

Testnet deployment:

export HYPERCORE_WRITER_TEST_PRIVATE_KEY="0x..."

# Deploy vault and deposit 5 USDC (testnet)
NETWORK=testnet ACTION=deposit python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

# Next day: withdraw from the same vault after lock-up expires
NETWORK=testnet ACTION=withdraw USDC_AMOUNT=5 LAGOON_VAULT=0x... TRADING_STRATEGY_MODULE=0x... python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

Troubleshooting

If a deposit lands on HyperEVM but the vault position is missing, use the check-hypercore-user.py diagnostic script to inspect the Safe’s HyperCore state:

ADDRESS=<safe_address> NETWORK=mainnet python scripts/hyperliquid/check-hypercore-user.py

This shows the Safe’s spot balances, EVM escrows, perp account and vault positions on HyperCore, helping diagnose where the USDC ended up.

API documentation

Source code

"""Deploy a Lagoon vault on HyperEVM and exercise Hypercore vault deposit/withdrawal.

A quick example script to test/simulate Lagoon vault deployment on HyperEVM.
The deployment script deals with the lack of deployed protocol contracts (Lagoon, Safe) on HyperEVM testnet,
and also deals with HyperEVM big block limitation.

We recommend using HyperEVM mainnet for testing. It's cheap and it will be more hassle/costly to fund HyperEVM testnet accounts.

Because of the big block usage, this script may take several minutes to run.

In ``SIMULATE`` mode the script forks the selected network via Anvil and deploys
mock CoreWriter/CoreDepositWallet contracts so no real funds are needed.
Without ``SIMULATE`` the script connects to the live network and requires a
funded deployer key.

You can also reconnect to an existing Lagoon deployment by setting
``LAGOON_VAULT`` and ``TRADING_STRATEGY_MODULE`` environment variables.
This is useful for the testnet withdrawal test: deploy + deposit on day 1,
then come back on day 2 with the same addresses to run withdrawal only.

Account funding for HyperEVM testnet
------------------------------------

1. Create a new private key and set ``HYPERCORE_WRITER_TEST_PRIVATE_KEY`` env
2. Move ~$2 worth of ETH on Arbitrum to that address
3. Move ~$5 worth of USDC on Arbitrum to that address
4. Sign in to https://app.hyperliquid.xyz with the new account
5. Deposit $5 USDC (minimum)
6. Now you have an account on Hyperliquid mainnet
7. Visit https://app.hyperliquid-testnet.xyz/drip and claim
8. Now you have 1000 USDC on the Hypercore testnet
9. Buy 1 HYPE with the mock USDC (set max slippage to 99%,
   testnet orderbook is illiquid)
10. Visit https://app.hyperliquid-testnet.xyz/portfolio — click EVM <-> CORE
11. Move 100 USDC to HyperEVM testnet
12. Move 0.01 HYPE to HyperEVM testnet
13. Check HyperEVM testnet balance on EVM <-> CORE dialog
    (there is no working HyperEVM testnet explorer)

Environment variables
---------------------
- ``NETWORK``: ``mainnet`` or ``testnet`` (default: ``testnet``).
  Selects the RPC URL and chain parameters.
- ``JSON_RPC_HYPERLIQUID``: HyperEVM mainnet RPC URL.
  Read from environment when ``NETWORK=mainnet``.
- ``JSON_RPC_HYPERLIQUID_TESTNET``: HyperEVM testnet RPC URL.
  Defaults to ``https://rpc.hyperliquid-testnet.xyz/evm``
  when ``NETWORK=testnet``.
- ``HYPERCORE_WRITER_TEST_PRIVATE_KEY``: Deployer private key (required on live network;
  defaults to Anvil account #0 in SIMULATE mode)
- ``SIMULATE``: Set to any value to fork via Anvil (default: unset)
- ``ACTION``: ``deposit``, ``withdraw``, or ``both`` (default: ``both``).
  On testnet you may need to wait 1 day between deposit and withdrawal
  due to the vault lock-up period, so run deposit first, then withdraw
  later.
- ``HYPERCORE_VAULT``: Hypercore vault address to deposit into.
  Defaults to the HLP vault for the selected network
  (testnet: ``0xa15099a30bbf2e68942d6f4c43d70d04faeab0a0``,
  mainnet: ``0xdfc24b077bc1425ad1dea75bcb6f8158e10df303``).
- ``USDC_AMOUNT``: USDC amount in human units for the vault deposit (default: ``2``).
  On live networks, an additional activation USDC is automatically added for
  HyperCore account activation (1 USDC creation fee + remainder reaches spot).
- ``ACTIVATION_AMOUNT``: USDC amount in human units for HyperCore account
  activation (default: ``2``). Increase to 5 if activation fails on testnet.
- ``ACTIVATION_TIMEOUT``: Seconds to wait for activation verification
  (default: ``60``). Increase to 180 on testnet.
- ``DEPOSIT_MODE``: ``two_phase`` (default) or ``batched``.
  ``two_phase`` splits bridge and vault deposit with an escrow wait;
  ``batched`` uses a single multicall but can silently fail under load.
- ``LOG_LEVEL``: Logging level (default: ``info``)

Reconnecting to an existing deployment:

- ``LAGOON_VAULT``: Existing Lagoon vault address. When set, skips
  deployment and whitelisting entirely.
- ``TRADING_STRATEGY_MODULE``: Existing TradingStrategyModuleV0 address
  (required when ``LAGOON_VAULT`` is set).

Usage::

    # Simulate on Anvil fork (no real funds needed)
    SIMULATE=true python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

    # Simulate testnet on Anvil fork
    SIMULATE=true NETWORK=testnet python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

    # Testnet deposit only (deploy + deposit, wait 1 day before withdrawal)
    NETWORK=testnet USDC_AMOUNT=1 python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

    # Mainnet deposit only
    NETWORK=mainnet ACTION=deposit USDC_AMOUNT=1 python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

    # Testnet withdrawal (reconnect to existing deployment after lock-up)
    HYPERCORE_WRITER_TEST_PRIVATE_KEY=0x... NETWORK=testnet \
        ACTION=withdraw HYPERCORE_VAULT=0xabc... USDC_AMOUNT=5 \
        LAGOON_VAULT=0xdef... TRADING_STRATEGY_MODULE=0x123... \
        poetry run python scripts/hyperliquid/deploy-lagoon-hyperliquid-vault.py

Troubleshooting
---------------

If deposit lands on EVM but the vault position is missing, use
``check-hypercore-user.py`` to inspect the Safe's HyperCore state
(spot balances, EVM escrows, perp account, vault positions)::

    ADDRESS=<safe_address> NETWORK=mainnet \
        poetry run python scripts/hyperliquid/check-hypercore-user.py

Common issues:

- **Account activation fails on testnet**: ``depositFor`` to contract
  addresses (Safe multisigs) does not create HyperCore accounts on testnet.
  The EVM transaction succeeds but USDC is lost. Use mainnet instead.
  See https://github.com/hyperliquid-dex/node/issues/138
- **USDC stuck in EVM escrow**: the bridge step succeeded but HyperCore
  has not yet processed the deposit. Wait and re-check.
- **USDC in spot but no vault position**: ``transferUsdClass`` or
  ``vaultTransfer`` failed silently on HyperCore. Re-submit phase 2.
- **USDC in perp but no vault position**: ``vaultTransfer`` failed
  (e.g. vault lock-up, wrong vault address). Check vault address.

For more information see `README-Hypercore-guard.md`.
"""

import logging
import os
import random
import time
from decimal import Decimal

from eth_account import Account
from eth_typing import HexAddress, HexStr
from safe_eth.safe.safe import Safe
from tabulate import tabulate
from web3 import Web3

from eth_defi.erc_4626.vault_protocol.lagoon.deployment import LAGOON_BEACON_PROXY_FACTORIES, LagoonConfig, LagoonDeploymentParameters, deploy_automated_lagoon_vault
from eth_defi.erc_4626.vault_protocol.lagoon.vault import LagoonVault
from eth_defi.gas import estimate_gas_price
from eth_defi.hotwallet import HotWallet
from eth_defi.hyperliquid.api import fetch_user_vault_equities
from eth_defi.hyperliquid.core_writer import build_hypercore_deposit_multicall, build_hypercore_deposit_phase1, build_hypercore_deposit_phase2, build_hypercore_withdraw_multicall
from eth_defi.hyperliquid.evm_escrow import DEFAULT_ACTIVATION_AMOUNT, activate_account, is_account_activated, wait_for_evm_escrow_clear
from eth_defi.hyperliquid.session import HYPERLIQUID_API_URL, HYPERLIQUID_TESTNET_API_URL, create_hyperliquid_session
from eth_defi.hyperliquid.testing import setup_anvil_hypercore_mocks
from eth_defi.provider.anvil import ANVIL_PRIVATE_KEY, fork_network_anvil, fund_erc20_on_anvil
from eth_defi.provider.multi_provider import create_multi_provider_web3
from eth_defi.safe.execute import execute_safe_tx
from eth_defi.safe.safe_compat import create_safe_ethereum_client
from eth_defi.token import USDC_NATIVE_TOKEN, fetch_erc20_details
from eth_defi.trace import TransactionAssertionError, assert_transaction_success_with_explanation
from eth_defi.utils import setup_console_logging
from eth_defi.vault.base import VaultSpec

logger = logging.getLogger(__name__)

#: Default Hypercore vault address per network (HLP on each network)
DEFAULT_VAULTS = {
    "testnet": "0xa15099a30bbf2e68942d6f4c43d70d04faeab0a0",
    "mainnet": "0xdfc24b077bc1425ad1dea75bcb6f8158e10df303",
}

#: Default public RPC for HyperEVM testnet
HYPERLIQUID_TESTNET_RPC = "https://rpc.hyperliquid-testnet.xyz/evm"


def _print_hypercore_balances(
    safe_address: str,
    network: str,
    simulate: bool,
) -> list:
    """Query Hyperliquid info API and print the Safe's Hypercore vault balances.

    Skipped in SIMULATE mode (Anvil mocks CoreWriter, no real Hypercore state).

    :return:
        List of :py:class:`~eth_defi.hyperliquid.api.UserVaultEquity` positions,
        or empty list in simulate mode.
    """
    if simulate:
        logger.info("Skipping Hypercore balance check in SIMULATE mode")
        return []

    api_url = HYPERLIQUID_TESTNET_API_URL if network == "testnet" else HYPERLIQUID_API_URL
    session = create_hyperliquid_session(api_url=api_url)
    equities = fetch_user_vault_equities(session, user=safe_address)
    if equities:
        rows = [[eq.vault_address, f"{eq.equity:,.6f}", eq.locked_until.isoformat()] for eq in equities]
        print("\nHypercore vault balances (Safe):")
        print(tabulate(rows, headers=["Vault", "Equity (USDC)", "Locked until (UTC)"], tablefmt="simple"))
    else:
        print("\nHypercore vault balances: none (Safe has no vault deposits on Hypercore)")

    return equities


def _do_deposit(
    lagoon_vault,
    usdc_amount: int,
    hypercore_amount: int,
    vault_address: str,
    deployer: HotWallet,
    usdc_human: int,
    network: str,
    simulate: bool,
    deposit_mode: str = "batched",
    activation_amount: int = DEFAULT_ACTIVATION_AMOUNT,
    activation_timeout: float = 60.0,
):
    """Execute deposit into a Hypercore vault via Lagoon.

    Supports two deposit modes (set via ``DEPOSIT_MODE`` env var):

    - ``batched`` (default): Single multicall with all 4 steps. The Safe
      must be activated on HyperCore first (done automatically).
    - ``two_phase``: Splits into bridge (phase 1) + escrow wait +
      vault deposit (phase 2). Safer under heavy HyperCore load.

    In SIMULATE mode, always uses batched (Anvil mocks, no real escrow).
    """
    web3 = lagoon_vault.web3

    # Build session with the correct API URL for escrow polling
    api_url = HYPERLIQUID_TESTNET_API_URL if network == "testnet" else HYPERLIQUID_API_URL
    session = create_hyperliquid_session(api_url=api_url) if not simulate else None

    # On live networks, ensure the Safe is activated on HyperCore
    if not simulate:
        if not is_account_activated(web3, user=lagoon_vault.safe_address):
            logger.info("Safe %s not activated on HyperCore, activating...", lagoon_vault.safe_address)
            activate_account(
                web3=web3,
                lagoon_vault=lagoon_vault,
                deployer=deployer,
                session=session,
                activation_amount=activation_amount,
                timeout=activation_timeout,
            )
            deployer.sync_nonce(web3)

    if not simulate:
        assert deposit_mode != "batched", "Batched deposit mode is disabled on live networks. The batched multicall puts all 4 steps (approve, CDW.deposit, transferUsdClass, vaultTransfer) into a single EVM block. Steps 3-4 are CoreWriter actions that depend on the CDW bridge (step 2) having cleared the EVM escrow, but because they land in the same block, HyperCore may process the CoreWriter actions before the bridge clears — causing steps 3-4 to silently fail while the EVM transaction succeeds. The USDC ends up stuck in spot or perp with no vault position. Use DEPOSIT_MODE=two_phase (default) which waits for the escrow to clear between the bridge and the CoreWriter actions."

    if simulate or deposit_mode == "batched":
        # Batched: single multicall with all 4 steps (simulate only)
        label = "batched (simulate)" if simulate else "batched"
        logger.info("Executing %s deposit (%d USDC)...", label, usdc_human)

        fn = build_hypercore_deposit_multicall(
            lagoon_vault=lagoon_vault,
            evm_usdc_amount=usdc_amount,
            hypercore_usdc_amount=hypercore_amount,
            vault_address=vault_address,
            check_activation=not simulate,
        )

        if simulate:
            tx_hash = fn.transact({"from": deployer.address})
        else:
            tx_hash = deployer.transact_and_broadcast_with_contract(fn)

        receipt = assert_transaction_success_with_explanation(web3, tx_hash)
        deposit_results = [
            ["Transaction", tx_hash.hex()],
            ["Gas used", receipt["gasUsed"]],
            ["Block", receipt["blockNumber"]],
            ["USDC amount", f"{usdc_human:,}"],
            ["Vault", vault_address],
            ["Mode", label],
        ]
        print("\nDeposit results:")
        print(tabulate(deposit_results, tablefmt="simple"))

    elif deposit_mode == "two_phase":
        # Two-phase deposit: bridge first, wait for escrow, then batch
        # the spot-to-perp and perp-to-vault CoreWriter actions together.

        # Phase 1: bridge USDC from EVM to HyperCore spot
        logger.info("Phase 1: bridging %d USDC to HyperCore spot...", usdc_human)
        fn1 = build_hypercore_deposit_phase1(
            lagoon_vault=lagoon_vault,
            evm_usdc_amount=usdc_amount,
        )
        tx_hash = deployer.transact_and_broadcast_with_contract(fn1)
        receipt = assert_transaction_success_with_explanation(web3, tx_hash)
        logger.info("Phase 1 tx: %s (gas: %d)", tx_hash.hex(), receipt["gasUsed"])

        # Wait for EVM escrow to clear
        logger.info("Waiting for EVM escrow to clear...")
        wait_for_evm_escrow_clear(session, user=lagoon_vault.safe_address)

        # Phase 2: move USDC from spot to perp and deposit into vault
        logger.info("Phase 2: transferUsdClass + vaultTransfer...")
        deployer.sync_nonce(web3)
        fn2 = build_hypercore_deposit_phase2(
            lagoon_vault=lagoon_vault,
            hypercore_usdc_amount=hypercore_amount,
            vault_address=vault_address,
        )
        tx_hash = deployer.transact_and_broadcast_with_contract(fn2)
        receipt = assert_transaction_success_with_explanation(web3, tx_hash)

        deposit_results = [
            ["Phase 2 tx", tx_hash.hex()],
            ["Gas used", receipt["gasUsed"]],
            ["Block", receipt["blockNumber"]],
            ["USDC amount", f"{usdc_human:,}"],
            ["Vault", vault_address],
            ["Mode", "two_phase"],
        ]
        print("\nDeposit results:")
        print(tabulate(deposit_results, tablefmt="simple"))

    else:
        raise ValueError(f"Unknown deposit mode: {deposit_mode!r} (expected 'batched' or 'two_phase')")

    # CoreWriter actions are asynchronous: the EVM transaction only queues
    # the action, and HyperCore processes it with a few seconds delay.
    # Wait before checking balances so the deposit has time to land.
    if not simulate:
        logger.info("Waiting 10s for CoreWriter actions to settle on HyperCore...")
        time.sleep(10)
    equities = _print_hypercore_balances(lagoon_vault.safe_address, network, simulate)
    if not simulate:
        assert len(equities) > 0, f"Deposit failed: Safe {lagoon_vault.safe_address} has no vault positions on HyperCore after deposit"


def _do_withdraw(
    lagoon_vault,
    hypercore_amount: int,
    vault_address: str,
    deployer: HotWallet,
    usdc_human: int,
    network: str,
    simulate: bool,
):
    """Execute withdrawal via multicall."""
    web3 = lagoon_vault.web3
    logger.info("Executing multicall withdrawal (%d USDC)...", usdc_human)
    fn = build_hypercore_withdraw_multicall(
        lagoon_vault=lagoon_vault,
        hypercore_usdc_amount=hypercore_amount,
        vault_address=vault_address,
    )
    try:
        if simulate:
            tx_hash = fn.transact({"from": deployer.address})
        else:
            tx_hash = deployer.transact_and_broadcast_with_contract(fn)
        receipt = assert_transaction_success_with_explanation(web3, tx_hash)
        withdraw_results = [
            ["Transaction", tx_hash.hex()],
            ["Gas used", receipt["gasUsed"]],
            ["Block", receipt["blockNumber"]],
            ["USDC amount", f"{usdc_human:,}"],
        ]
        print("\nWithdrawal results:")
        print(tabulate(withdraw_results, tablefmt="simple"))
    except (TransactionAssertionError, Exception) as e:
        logger.warning(
            "Withdrawal failed (expected if vault has lock-up period): %s",
            str(e)[:200],
        )
        print(f"\nWithdrawal skipped: {str(e)[:200]}")
        return

    # CoreWriter actions are asynchronous: the EVM transaction only queues
    # the action, and HyperCore processes it with a few seconds delay.
    # Wait before checking balances so the withdrawal has time to settle.
    if not simulate:
        logger.info("Waiting 10s for CoreWriter actions to settle on HyperCore...")
        time.sleep(10)
    _print_hypercore_balances(lagoon_vault.safe_address, network, simulate)

    # Transfer remaining USDC from Safe back to deployer via Safe
    # multisig execTransaction (bypasses the guard, which would block
    # performCall transfers to non-whitelisted receivers).
    usdc_token = lagoon_vault.underlying_token
    safe_balance = usdc_token.fetch_balance_of(lagoon_vault.safe_address)
    if safe_balance > 0:
        logger.info("Transferring %s USDC from Safe back to deployer %s", safe_balance, deployer.address)
        raw_amount = usdc_token.convert_to_raw(safe_balance)
        transfer_data = usdc_token.contract.functions.transfer(
            deployer.address,
            raw_amount,
        ).build_transaction({"from": lagoon_vault.safe_address})["data"]

        if simulate:
            # In Anvil simulate mode, impersonate the Safe and call the
            # USDC contract directly (bypasses signer requirement).
            # Fund the Safe with HYPE for gas first.
            web3.provider.make_request("anvil_setBalance", [lagoon_vault.safe_address, hex(10**18)])
            web3.provider.make_request("anvil_impersonateAccount", [lagoon_vault.safe_address])
            tx_hash = web3.eth.send_transaction(
                {
                    "from": lagoon_vault.safe_address,
                    "to": usdc_token.address,
                    "data": transfer_data,
                }
            )
            web3.provider.make_request("anvil_stopImpersonatingAccount", [lagoon_vault.safe_address])
        else:
            ethereum_client = create_safe_ethereum_client(web3)
            safe = Safe(lagoon_vault.safe_address, ethereum_client)
            safe_tx = safe.build_multisig_tx(
                usdc_token.address,
                0,
                bytes.fromhex(transfer_data[2:]),
            )
            safe_tx.sign(deployer.private_key.hex())
            gas_estimate = estimate_gas_price(web3)
            deployer.sync_nonce(web3)
            tx_hash, _tx = execute_safe_tx(
                safe_tx,
                tx_sender_private_key=deployer.private_key.hex(),
                tx_gas=100_000,
                tx_nonce=deployer.allocate_nonce(),
                gas_fee=gas_estimate,
            )
        assert_transaction_success_with_explanation(web3, tx_hash)
        logger.info("USDC returned to deployer: tx %s", tx_hash.hex())


def main():
    log_level = os.environ.get("LOG_LEVEL", "info")
    setup_console_logging(default_log_level=log_level)

    network = os.environ.get("NETWORK", "testnet").lower()
    assert network in ("mainnet", "testnet"), f"NETWORK must be 'mainnet' or 'testnet', got '{network}'"

    if network == "testnet":
        json_rpc = os.environ.get("JSON_RPC_HYPERLIQUID_TESTNET", HYPERLIQUID_TESTNET_RPC)
    else:
        json_rpc = os.environ.get("JSON_RPC_HYPERLIQUID")
        assert json_rpc, "JSON_RPC_HYPERLIQUID environment variable required for mainnet"

    simulate = os.environ.get("SIMULATE")
    action = os.environ.get("ACTION", "both").lower()
    assert action in ("deposit", "withdraw", "both"), f"ACTION must be 'deposit', 'withdraw', or 'both', got '{action}'"

    private_key = os.environ.get("HYPERCORE_WRITER_TEST_PRIVATE_KEY", ANVIL_PRIVATE_KEY if simulate else None)
    assert private_key, "HYPERCORE_WRITER_TEST_PRIVATE_KEY environment variable required (or set SIMULATE=true)"

    vault_address = HexAddress(HexStr(os.environ.get("HYPERCORE_VAULT", DEFAULT_VAULTS[network])))
    # Minimum vault deposit is 5 USDC
    usdc_human = int(os.environ.get("USDC_AMOUNT", "5"))
    deposit_mode = os.environ.get("DEPOSIT_MODE", "two_phase").lower()
    assert deposit_mode in ("batched", "two_phase"), f"DEPOSIT_MODE must be 'batched' or 'two_phase', got '{deposit_mode}'"

    # Activation parameters (configurable for testnet troubleshooting)
    activation_human = int(os.environ.get("ACTIVATION_AMOUNT", "2"))
    activation_timeout = float(os.environ.get("ACTIVATION_TIMEOUT", "60"))

    # Existing deployment addresses (skip deploy + whitelist when set)
    existing_lagoon_vault = os.environ.get("LAGOON_VAULT")
    existing_module = os.environ.get("TRADING_STRATEGY_MODULE")

    if existing_lagoon_vault:
        assert existing_module, "TRADING_STRATEGY_MODULE required when LAGOON_VAULT is set"

    # Connect to network
    anvil = None
    if simulate:
        logger.info("SIMULATE mode: forking HyperEVM %s via Anvil (RPC: %s)", network, json_rpc)
        anvil = fork_network_anvil(
            json_rpc,
            gas_limit=30_000_000,
        )
        web3 = create_multi_provider_web3(anvil.json_rpc_url, default_http_timeout=(3, 500.0))
    else:
        logger.info("Live %s mode (RPC: %s)", network, json_rpc)
        web3 = create_multi_provider_web3(json_rpc, default_http_timeout=(3, 500.0))

    chain_id = web3.eth.chain_id
    logger.info("Connected to chain %d, block %d", chain_id, web3.eth.block_number)

    deployer_account = Account.from_key(private_key)
    deployer = HotWallet(deployer_account)
    deployer.sync_nonce(web3)
    logger.info("Deployer: %s", deployer.address)

    usdc_address = USDC_NATIVE_TOKEN[chain_id]
    usdc = fetch_erc20_details(web3, usdc_address)
    usdc_amount = usdc.convert_to_raw(usdc_human)
    hypercore_amount = usdc_amount  # Hypercore uses same decimals as EVM USDC

    # On live networks doing deposits, the Safe needs extra USDC for
    # HyperCore account activation (1 USDC fee + remainder reaches spot).
    # In simulate mode, activation is skipped (no real HyperCore state).
    activation_raw = int(activation_human * 10**6) if (not simulate and action in ("deposit", "both")) else 0
    total_safe_funding_raw = usdc_amount + activation_raw
    total_safe_funding_human = usdc_human + (activation_raw / 10**6)

    # Check deployer has enough HYPE (gas) and USDC before doing anything expensive
    if not simulate:
        hype_balance = web3.eth.get_balance(deployer_account.address)
        hype_human = hype_balance / 10**18
        min_hype = 0.1
        assert hype_human >= min_hype, f"Deployer {deployer_account.address} has {hype_human:.4f} HYPE, need at least {min_hype} HYPE for gas"

        deployer_usdc_human = usdc.fetch_balance_of(deployer_account.address)
        min_usdc = total_safe_funding_human
        assert deployer_usdc_human >= min_usdc, f"Deployer {deployer_account.address} has {deployer_usdc_human:.2f} USDC, need at least {min_usdc:.0f} USDC ({usdc_human} deposit + {activation_human:.0f} activation)"
        logger.info("Deployer balances: %.4f HYPE, %.2f USDC", hype_human, deployer_usdc_human)

    # Track HYPE (gas) usage across all phases
    hype_start = web3.eth.get_balance(deployer_account.address)

    if existing_lagoon_vault:
        # Reconnect to an existing Lagoon deployment
        logger.info("Reconnecting to existing deployment: vault=%s module=%s", existing_lagoon_vault, existing_module)
        lagoon_vault = LagoonVault(
            web3,
            VaultSpec(chain_id, existing_lagoon_vault),
            trading_strategy_module_address=existing_module,
            default_block_identifier="latest",
        )
        safe_address = lagoon_vault.safe_address
        module = lagoon_vault.trading_strategy_module
        logger.info("Vault:  %s", lagoon_vault.vault_address)
        logger.info("Safe:   %s", safe_address)
        logger.info("Module: %s", module.address)
    else:
        # Fresh deployment via deploy_automated_lagoon_vault()
        if simulate:
            setup_anvil_hypercore_mocks(web3, deployer_account.address)

        logger.info("Deploying Lagoon vault...")
        # Deploy from scratch when there is no pre-deployed factory on the chain.
        # Testnet (998) has no factory; mainnet (999) has an OptinProxyFactory
        # at 0x90beB507A1BA7D64633540cbce615B574224CD84 so we use it.
        from_the_scratch = chain_id not in LAGOON_BEACON_PROXY_FACTORIES
        assert not (from_the_scratch and network == "mainnet"), f"Mainnet (chain {chain_id}) should have a Lagoon factory in LAGOON_BEACON_PROXY_FACTORIES — from-scratch deployment is not supported on mainnet"
        if from_the_scratch:
            logger.info("No Lagoon factory on chain %d, deploying from scratch", chain_id)

        config = LagoonConfig(
            parameters=LagoonDeploymentParameters(
                underlying=usdc_address,
                name="HyperEVM Hypercore Manual Test",
                symbol="TEST",
            ),
            asset_manager=None,
            asset_managers=[deployer_account.address],
            safe_owners=[deployer_account.address],
            safe_threshold=1,
            any_asset=False,
            hypercore_vaults=[vault_address],
            safe_salt_nonce=random.randint(0, 1000) if not from_the_scratch else None,
            from_the_scratch=from_the_scratch,
            use_forge=from_the_scratch,  # Required for from_the_scratch
            between_contracts_delay_seconds=8.0,  # Speed up deployment by waiting less
        )

        deploy_info = deploy_automated_lagoon_vault(
            web3=web3,
            deployer=deployer,
            config=config,
        )

        lagoon_vault = deploy_info.vault
        module = deploy_info.trading_strategy_module
        safe_address = deploy_info.safe.address

        logger.info("Vault:  %s", lagoon_vault.vault_address)
        logger.info("Safe:   %s", safe_address)
        logger.info("Module: %s", module.address)

        hype_after_deploy = web3.eth.get_balance(deployer_account.address)
        deploy_cost = (hype_start - hype_after_deploy) / 10**18
        logger.info("Deployment gas cost: %.6f HYPE", deploy_cost)

        # Fund Safe with USDC (deposit amount + activation overhead on live)
        if simulate:
            fund_erc20_on_anvil(web3, usdc_address, safe_address, usdc_amount)
        else:
            # Wait for RPC nodes to catch up with the latest nonce after
            # deployment, then sync HotWallet's internal nonce counter
            time.sleep(2)
            deployer.sync_nonce(web3)

            # Transfer USDC from deployer to Safe on live network.
            # Includes activation overhead (2 USDC) so the Safe has enough
            # for both account activation and the vault deposit.
            safe_balance = usdc.fetch_balance_of(safe_address)
            if safe_balance < total_safe_funding_human:
                transfer_amount = Decimal(str(total_safe_funding_human)) - safe_balance
                logger.info(
                    "Transferring %s USDC from deployer to Safe %s (%d deposit + %d activation)",
                    transfer_amount,
                    safe_address,
                    usdc_human,
                    activation_raw // 10**6,
                )
                tx_hash = deployer.transact_and_broadcast_with_contract(
                    usdc.transfer(safe_address, transfer_amount),
                    gas_limit=100_000,
                )
                assert_transaction_success_with_explanation(web3, tx_hash)
                logger.info("USDC transfer to Safe complete: tx %s", tx_hash.hex())

    balance = usdc.fetch_balance_of(safe_address)
    logger.info("Safe USDC balance: %s", balance)

    # In SIMULATE mode, impersonate the deployer so eth_sendTransaction works
    # (the deployer may not be an Anvil-unlocked account if HYPERCORE_WRITER_TEST_PRIVATE_KEY is set)
    if simulate:
        web3.provider.make_request("anvil_impersonateAccount", [deployer_account.address])

    if action in ("deposit", "both"):
        balance_raw = usdc.contract.functions.balanceOf(Web3.to_checksum_address(safe_address)).call()
        assert balance_raw >= total_safe_funding_raw, f"Safe USDC balance {balance} ({balance_raw} raw) insufficient, need {total_safe_funding_human} ({total_safe_funding_raw} raw): {usdc_human} deposit + {activation_human} activation"
        _do_deposit(
            lagoon_vault,
            usdc_amount,
            hypercore_amount,
            vault_address,
            deployer,
            usdc_human,
            network=network,
            simulate=bool(simulate),
            deposit_mode=deposit_mode,
            activation_amount=activation_raw,
            activation_timeout=activation_timeout,
        )

    if action in ("withdraw", "both"):
        _do_withdraw(
            lagoon_vault,
            hypercore_amount,
            vault_address,
            deployer,
            usdc_human,
            network=network,
            simulate=bool(simulate),
        )

    if simulate:
        web3.provider.make_request("anvil_stopImpersonatingAccount", [deployer_account.address])

    # Summary
    hype_end = web3.eth.get_balance(deployer_account.address)
    total_hype_spent = (hype_start - hype_end) / 10**18
    final_balance = usdc.fetch_balance_of(safe_address)
    summary = [
        ["Network", network],
        ["Vault", lagoon_vault.vault_address],
        ["Safe", safe_address],
        ["Module", module.address],
        ["Chain ID", chain_id],
        ["Action", action],
        ["USDC amount", f"{usdc_human:,}"],
        ["Final USDC balance", f"{final_balance:,.2f}"],
        ["HYPE spent (gas)", f"{total_hype_spent:.6f}"],
        ["Simulate", "yes" if simulate else "no"],
    ]
    print("\nSummary:")
    print(tabulate(summary, tablefmt="simple"))

    if anvil:
        anvil.close()
        logger.info("Anvil stopped")


if __name__ == "__main__":
    main()

Example output

Live mainnet mode (RPC: xxx)
Created provider edge.goldsky.com, using request args {'headers': {'Content-Type': 'application/json', 'User-Agent': 'web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider'}, 'timeout': (3, 500.0)}, headers {'Content-Type': 'application/json', 'User-Agent': 'web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider'}
Created provider hyperliquid-mainnet.g.alchemy.com, using request args {'headers': {'Content-Type': 'application/json', 'User-Agent': 'web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider'}, 'timeout': (3, 500.0)}, headers {'Content-Type': 'application/json', 'User-Agent': 'web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider'}
Created provider lb.drpc.org, using request args {'headers': {'Content-Type': 'application/json', 'User-Agent': 'web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider'}, 'timeout': (3, 500.0)}, headers {'Content-Type': 'application/json', 'User-Agent': 'web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider'}
Configuring MultiProviderWeb3. Call providers: ['edge.goldsky.com', 'hyperliquid-mainnet.g.alchemy.com', 'lb.drpc.org'], transact providers -
Connected to chain 999, block 28053777
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 214
Deployer: 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c
Deployer balances: 1.8148 HYPE, 10.90 USDC
Deploying Lagoon vault...
Beginning Lagoon vault deployment, legacy mode: False, ABI is lagoon/v0.5.0/Vault.json
Deploying Safe with deterministic address (CREATE2).
Initial cosigner list: ['0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c']
Initial threshold: 1
Salt nonce: 762
Expected deterministic Safe address: 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A
Sleeping for 10 seconds for Safe deployment state to propagate
Safe deployed at deterministic address 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A
Deployed new Safe: 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A
Between contracts deployment delay: Sleeping 8.0 for new nonce to propagade
Deploying Lagoon vault on chain 999, deployer is <eth_account.signers.local.LocalAccount object at 0x129c2e360>, legacy is False
Wrapped native token is: 0x5555555555555555555555555555555555555555
Transacting with OptinBeaconFactory contract 0x90beB507A1BA7D64633540cbce615B574224CD84.createVaultProxy() with args ['0x0000000000000000000000000000000000000000', '0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A', 259200, ['0xb88339CB7199b77E23DB6E890353E22632Ba630f', 'HyperEVM Hypercore Manual Test', 'TEST', '0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A', '0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A', '0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c', '0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A', '0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A', 200, 2000, False, 86400], '0x0101010101010101010101010101010101010101010101010101010101010101']
Between contracts deployment delay: Sleeping 8.0 for new nonce to propagade
Deploying TradingStrategyModuleV0
CowSwapLib not needed, linking with zero address
GmxLib not needed, linking with zero address
Deploying HypercoreVaultLib for HyperEVM chain 999
Setting big blocks enabled for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c on mainnet
Big blocks API response: {'status': 'ok', 'response': {'type': 'default'}}
Setting big blocks disabled for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c on mainnet
Big blocks API response: {'status': 'ok', 'response': {'type': 'default'}}
Deployed HypercoreVaultLib at 0x2078aFf0dD0362B139722aB48C8C09d818530a24 for HyperEVM
Setting big blocks enabled for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c on mainnet
Big blocks API response: {'status': 'ok', 'response': {'type': 'default'}}
Deploying TradingStrategyModuleV0 with libraries {'CowSwapLib': '0x0000000000000000000000000000000000000000', 'GmxLib': '0x0000000000000000000000000000000000000000', 'HypercoreVaultLib': '0x2078aFf0dD0362B139722aB48C8C09d818530a24'} and gas 10000000
Only 1 RPC provider configured: edge.goldsky.com, cannot switch, sleeping and hoping the issue resolves itself
Encountered JSON-RPC retryable error {'code': -32603, 'message': 'upstream responded emptyish: null'}
When calling RPC method: eth_getTransactionReceipt('0x4fc8697f032540ff8b73c888b2c6fa926fa8af7e83aa7b1a46fbc03664105466',)
Headers are: {'content-encoding': 'gzip',
'content-type': 'application/json',
'date': 'Mon, 23 Feb 2026 12:04:30 GMT',
'endpoint_uri': 'edge.goldsky.com',
'fly-request-id': '01KJ565TWY3R7PVE3B920K3R38-cdg',
'headers-track-id': '53-8811291200',
'method': 'eth_getTransactionReceipt',
'server': 'Fly/84caf4a9 (2026-02-18)',
'status_code': 200,
'traceparent': '00-1150cf49be6c9db344e0f75522f33ec8-2a7ab7831e3c6afe-00',
'transfer-encoding': 'chunked',
'via': '1.1 fly.io',
'x-erpc-commit': '',
'x-erpc-machine': 'd8d14e6c00e938',
'x-erpc-region': 'cdg',
'x-erpc-version': ''}
Retrying in 5.000000 seconds, retry #1 / 6
Setting big blocks disabled for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c on mainnet
Big blocks API response: {'status': 'ok', 'response': {'type': 'default'}}
Enabling TradingStrategyModuleV0 on Safe multisig
Using gas estimate: {'Base Fee': '0.12G (119,099,107)',
'Max fee per gas': '0.52G (515,645,999)',
'Max priority fee per gas': '0.22G (222,200,000)'}
Between contracts deployment delay: Sleeping 8.0 for new nonce to propagade
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 219
Setting up TradingStrategyModuleV0 guard: 0x6150c316cA5bce03948d62eBa1f23a5892AE53b4
Whitelisting trade-executor as sender
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 219
Sleeping for 2 seconds to wait for nonce to propagate
Whitelist Safe as trade receiver
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 220
Sleeping for 2 seconds to wait for nonce to propagate
Not whitelisted: Uniswap v2
Not whitelisted: Uniswap v3
Not whitelisted: Aave v3
Not whitelisted: Orderly vault
Not whitelisted: any ERC-4626 vaults
Not whitelisting specific ERC-20 tokens
Not whitelisted: GMX
Not whitelisted: CCTP
Whitelisting Hypercore: CoreWriter=0x3333333333333333333333333333333333333333, CoreDepositWallet=0x6B9E773128f453f5c2C60935Ee2DE2CBc5390A24
Whitelisting Hypercore vault #1: 0xdfc24b077bc1425ad1dea75bcb6f8158e10df303
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 221
Sleeping for 2 seconds to wait for nonce to propagate
Hypercore whitelisting complete: 1 vault(s)
Using only whitelisted assets
Whitelist vault settlement
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 222
Sleeping for 2 seconds to wait for nonce to propagate
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 223
Sleeping for 2 seconds to wait for nonce to propagate
Using gas estimate: {'Base Fee': '0.15G (154,978,757)',
'Max fee per gas': '0.60G (596,016,415)',
'Max priority fee per gas': '0.22G (222,200,000)'}
Gnosis GS206 sync issue sleep 20.0 seconds
Updating Safe owner list: ['0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c'] with threshold 1
Deployer: already exist on Safe cosigner
Changing signing threshold to: 1
Using gas estimate: {'Base Fee': '0.10G (100,000,000)',
'Max fee per gas': '1.16G (1,161,617,466)',
'Max priority fee per gas': '0.84G (837,158,452)'}
Owners updated
Vault:  0xd7A7768268a4010AF413f6c85D4901a79eFddd56
Safe:   0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A
Module: 0x6150c316cA5bce03948d62eBa1f23a5892AE53b4
Deployment gas cost: 0.005827 HYPE
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 226
Transferring 7.0 USDC from deployer to Safe 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A (5 deposit + 2 activation)
USDC transfer to Safe complete: tx 7dac5a171267dfa026c006715bf2bf57f5f01e8c31ecbf766883c2979df37d30
Safe USDC balance: 7
Account 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A coreUserExists on HyperCore: False
Safe 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A not activated on HyperCore, activating...
Account 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A coreUserExists on HyperCore: False
User 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A: 0 spot balance(s), 0 EVM escrow(s)
Activating account 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A on HyperCore via depositFor (2000000 raw USDC)
Lagoon: Wrapping call to TradingStrategyModuleV0  v0.1.4. Target: 0xb88339CB7199b77E23DB6E890353E22632Ba630f, function: approve (0x095ea7b3), args: ['0x6B9E773128f453f5c2C60935Ee2DE2CBc5390A24', '2000000'], payload is 68 bytes
Activation: USDC approve tx 959286ff0b38dcdf2f638ab08205e04b2a52d3935e0b536f6f9b6597a43d977f
Nonce sync failed, read onchain nonce 227 that is older than our current nonce: 228. This may happen if you have not broadcasted the last transaction yet or if the node fallbacks edge.goldsky.com, hyperliquid-mainnet.g.alchemy.com, lb.drpc.org is crappy.
Lagoon: Wrapping call to TradingStrategyModuleV0  v0.1.4. Target: 0x6B9E773128f453f5c2C60935Ee2DE2CBc5390A24, function: depositFor (0xc23c545a), args: ['0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A', '2000000', '4294967295'], payload is 100 bytes
Activation: depositFor tx adf4efb46f7aff9705bb15b8c5edd2cb24e77e226f898b9a38250d063047f979
Account 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A coreUserExists on HyperCore: True
Account 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A successfully activated on HyperCore
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 229
Phase 1: bridging 5 USDC to HyperCore spot...
Phase 1 tx: a3839c988b59e5f39fd3de0c4be252abaae2765180937e596d02798888554557 (gas: 125901)
Waiting for EVM escrow to clear...
User 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A: 1 spot balance(s), 0 EVM escrow(s)
EVM escrow cleared for 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A after 1 poll(s)
Phase 2: transferUsdClass + vaultTransfer...
Synced nonce for 0x4E6B7f7aFB2E23Bf9355c10e4454f73E6E6F3D9c to 230

Deposit results:
-----------  ----------------------------------------------------------------
Phase 2 tx   5b7cd08c93e3037ae44b13cc5da1b470d0de04842f3566d6b95b413634c21cba
Gas used     145955
Block        28053991
USDC amount  5
Vault        0xdfc24b077bc1425ad1dea75bcb6f8158e10df303
Mode         two_phase
-----------  ----------------------------------------------------------------
Waiting 10s for CoreWriter actions to settle on HyperCore...
User 0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A has 1 vault position(s)

Hypercore vault balances (Safe):
Vault                                         Equity (USDC)  Locked until (UTC)
------------------------------------------  ---------------  --------------------------
0xdfc24b077bc1425ad1dea75bcb6f8158e10df303                5  2026-02-27T12:06:22.064000

Summary:
------------------  ------------------------------------------
Network             mainnet
Vault               0xd7A7768268a4010AF413f6c85D4901a79eFddd56
Safe                0x5B7cf48FC9d2bE82DED36582dCC5da9c0950c96A
Module              0x6150c316cA5bce03948d62eBa1f23a5892AE53b4
Chain ID            999
Action              deposit
USDC amount         5
Final USDC balance  0.00
HYPE spent (gas)    0.006326
Simulate            no
------------------  ------------------------------------------