vault.base

Documentation for eth_defi.vault.base Python module.

Generic Vault adapter base classes.

  • Create unified interface across different vault protocols and their investment flows

  • Helps to create automated trading agents against any vault easily

  • Handle both trading (asset management role) and investor management (deposits/redemptions)

  • See VaultBase to get started

  • See RawVaultPriceRow for the raw scanner parquet row schema and CleanedVaultPriceRow for the enriched cleaned format

Functions

verify_parquet_file(path[, expected_rows, ...])

Read back a parquet file after writing and verify its integrity.

Classes

ParquetVerificationResult

Result of verifying a parquet file after writing.

RawVaultPriceRow

Schema for a single row in the uncleaned vault price parquet file.

TradingUniverse

Describe assets vault can manage.

VaultBase

Base class for vault protocol adapters.

VaultFlowManager

Manage deposit/redemption events.

VaultHistoricalRead

Vault share price and fee structure at the point of time.

VaultHistoricalReader

Support reading historical vault share prices.

VaultInfo

Vault-protocol specific intormation about the vault.

VaultPortfolio

Track assets and balances in a vault.

VaultReadCondition

VaultReadCondition()

VaultSpec

Unique id for a vault.

Exceptions

ParquetVerificationError

Raised when a parquet file fails post-write verification.

exception ParquetVerificationError

Bases: Exception

Raised when a parquet file fails post-write verification.

This is a hard failure — the operator must investigate and restore from backup. Never catch and swallow this exception.

__init__(*args, **kwargs)
__new__(**kwargs)
add_note(note, /)

Add a note to the exception

with_traceback(tb, /)

Set self.__traceback__ to tb and return self.

class ParquetVerificationResult

Bases: object

Result of verifying a parquet file after writing.

Returned by verify_parquet_file() on success.

__init__(path, row_count, column_count, file_size, column_names)
Parameters
Return type

None

class RawVaultPriceRow

Bases: TypedDict

Schema for a single row in the uncleaned vault price parquet file.

This is the format produced by VaultHistoricalRead.export() and written to vault-prices-1h.parquet by the historical scanner (scan_historical_prices_to_parquet()).

The canonical columns are defined by VaultHistoricalRead.to_pyarrow_schema(). Native protocol merges (Hyperliquid, GRVT, Lighter, Hibachi) may add extra columns (e.g. account_pnl, leader_fraction) that are preserved across schema migrations but are not part of this TypedDict.

See CleanedVaultPriceRow in eth_defi.research.wrangle_vault_prices for the enriched schema produced by the cleaning pipeline.

__init__(*args, **kwargs)
__new__(**kwargs)
clear()

Remove all items from the dict.

copy()

Return a shallow copy of the dict.

fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items()

Return a set-like object providing a view on the dict’s items.

keys()

Return a set-like object providing a view on the dict’s keys.

pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from mapping/iterable E and F.

If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values()

Return an object providing a view on the dict’s values.

class TradingUniverse

Bases: object

Describe assets vault can manage.

  • Because of brainrotten and awful ERC-20 token standard, the vault does not know what tokens it owns and this needs to be specific offchain

__init__(spot_token_addresses)
Parameters

spot_token_addresses (set[str]) –

Return type

None

class VaultBase

Bases: abc.ABC

Base class for vault protocol adapters.

  • Allows automated interaction with different vault protocols.

  • Contains various abstract methods that the implementation class must override

Supported protocols include

Code exists, but does not confirm the interface yet:

Vault covered functionality

  • Fetching the current balances, deposits or redemptions

  • Get vault information with fetch_info()
    • No standardised data structures or functions yet

  • Build a swap through a vault
    • No standardised data structure yet

  • Update vault position valuations
    • No standardised data structure yet

Integration check list

Integration tests needed for:

  • ☑️ read vault core info

  • ☑️ read vault investors

  • ☑️ read vault share price

  • ☑️ read vault share token

  • ☑️ read all positions

  • ☑️ read NAV

  • ☑️ read pending redemptions to know how much USDC we will need for the next settlement cycles

  • ☑️ deposit integration test

  • ☑️ redemption integration

  • ☑️ swap integration test

  • ☑️ re-valuation integration test

  • ☑️ only asset manager allowed to swap negative test

  • ☑️ only valuation commitee allowed to update vault valuations (if applicable)

  • ☑️ can redeem if enough USDC to settle

  • ☑️ cannot redeem not enough USDC to settle

For code examples see tests/lagoon and tests/velvet on the Github repository.

Parameters
  • token_cache

    Token cache for vault tokens.

    Allows to pass eth_defi.token.TokenDiskCache to speed up operations.

  • require_denomination_token

    If True, accessing denomination_token will raise RuntimeError when the on-chain lookup returns None.

    Use for deployment scripts and operational contexts where a missing denomination token is always a hard error.

__init__(token_cache=None, require_denomination_token=False)
Parameters
  • token_cache (Optional[dict]) –

    Token cache for vault tokens.

    Allows to pass eth_defi.token.TokenDiskCache to speed up operations.

  • require_denomination_token (bool) –

    If True, accessing denomination_token will raise RuntimeError when the on-chain lookup returns None.

    Use for deployment scripts and operational contexts where a missing denomination token is always a hard error.

abstract property address: eth_typing.evm.HexAddress

Vault contract address.

  • Often vault protocols need multiple contracts per vault, so what this function returns depends on the protocol

abstract property chain_id: int

Chain this vault is on

property denomination_token: Optional[eth_defi.token.TokenDetails]

Get the token which denominates the vault valuation

  • Used in deposits and redemptions

  • Used in NAV calculation

  • Used in profit benchmarks

  • Usually USDC

Returns

Token wrapper instance.

Maybe None for broken vaults like https://arbiscan.io/address/0x9d0fbc852deccb7dcdd6cb224fa7561efda74411#code

Note

None results are not cached — the next access will retry the on-chain call. This avoids permanently caching a transient RPC failure.

property deposit_manager: eth_defi.vault.deposit_redeem.VaultDepositManager

Deposit manager assocaited with this vault

property description: Optional[str]

Human-readable vault strategy description.

  • Fetched from protocol-specific offchain sources (e.g. Euler GitHub labels, Lagoon web app API)

  • Returns None if the protocol does not provide descriptions or the vault is not in the metadata source

  • Override in subclasses that support offchain metadata

fetch_available_liquidity(block_identifier='latest')

Get the amount of denomination token available for immediate withdrawal.

Only applicable to lending protocol vaults (IPOR, Euler, Morpho, Gearbox, etc.). Non-lending protocols should leave this method unimplemented.

Note: maxRedeem(address(0)) does NOT work as a proxy for available liquidity because it requires a specific address that has already deposited shares. For address(0), balanceOf is always 0, so maxRedeem returns 0 regardless of actual liquidity.

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) – Block to query. Defaults to “latest”.

Raises

NotImplementedError – For non-lending protocol vaults.

Returns

Amount in denomination token units (human-readable Decimal).

Return type

Optional[decimal.Decimal]

abstract fetch_denomination_token()

Read denomination token from onchain.

Use denomination_token() for cached access.

Return type

eth_defi.token.TokenDetails

fetch_denomination_token_address()

Get the address for the denomination token.

Triggers RCP call

Return type

eth_typing.evm.HexAddress

fetch_deposit_closed_reason()

Get human-readable reason why deposits are closed.

  • Override in protocol-specific subclasses

  • Default behaviour: assume deposits are always open (return None)

Returns

Human-readable string explaining why deposits are closed, or None if deposits are open.

Example reasons:

  • ”Epoch redemption window closed (opens in 14h)”

  • ”Vault paused by admin”

  • ”Max deposit cap reached”

  • ”Vault utilisation too high”

Return type

Optional[str]

fetch_deposit_next_open()

Get when deposits will next be open.

  • For epoch-based vaults (Ostium, D2), return calculated window open time

  • For non-epoch vaults (Plutus, IPOR, Morpho), return None

  • Override in protocol-specific subclasses

Returns

Naive UTC datetime when deposits will next be available, or None if:

  • Deposits are currently open

  • Timing is unpredictable (manually controlled)

  • Protocol does not support timing information

Return type

Optional[datetime.datetime]

abstract fetch_info()

Read vault parameters from the chain.

Use info() property for cached access.

Return type

eth_defi.vault.base.VaultInfo

abstract fetch_nav()

Fetch the most recent onchain NAV value.

Returns

Vault NAV, denominated in denomination_token()

Return type

decimal.Decimal

abstract fetch_portfolio(universe, block_identifier=None)

Read the current token balances of a vault.

  • SHould be supported by all implementations

Parameters
Return type

eth_defi.vault.base.VaultPortfolio

fetch_redemption_closed_reason()

Get human-readable reason why redemptions are closed.

  • Override in protocol-specific subclasses

  • Default behaviour: assume redemptions are always open (return None)

Returns

Human-readable string explaining why redemptions are closed, or None if redemptions are open.

Example reasons:

  • ”Epoch funding phase in progress (opens in 2d 5h)”

  • ”Vault paused by admin”

  • ”Vault utilisation too high - insufficient liquidity”

Return type

Optional[str]

fetch_redemption_next_open()

Get when withdrawals/redemptions will next be open.

  • For epoch-based vaults (Ostium, D2), return calculated window open time

  • For non-epoch vaults (Plutus, IPOR, Morpho), return None

  • Override in protocol-specific subclasses

Returns

Naive UTC datetime when withdrawals will next be available, or None if:

  • Withdrawals are currently open

  • Timing is unpredictable (manually controlled)

  • Protocol does not support timing information

Return type

Optional[datetime.datetime]

abstract fetch_share_token()

Read share token details onchain.

Use share_token() for cached access.

Return type

eth_defi.token.TokenDetails

fetch_utilisation_percent(block_identifier='latest')

Get the percentage of assets currently lent out.

Only applicable to lending protocol vaults (IPOR, Euler, Morpho, Gearbox, etc.). Non-lending protocols should leave this method unimplemented.

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) – Block to query. Defaults to “latest”.

Raises

NotImplementedError – For non-lending protocol vaults.

Returns

Utilisation as float between 0.0 and 1.0 (0% to 100%).

Return type

Optional[float]

property flow_manager: eth_defi.vault.base.VaultFlowManager

Flow manager associated with this vault

get_deposit_fee(block_identifier)

Deposit fee is set to zero by default as vaults usually do not have deposit fees.

Internal: Use get_fee_data().

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) –

Return type

Optional[float]

abstract get_deposit_manager()

Get deposit manager to deposit/redeem from the vault.

Return type

eth_defi.vault.deposit_redeem.VaultDepositManager

get_estimated_lock_up()

What is the estimated lock-up period for this vault.

Returns

None if not know

Return type

Optional[datetime.timedelta]

get_fee_data()

Get fee data structure for this vault.

Raises

ValueError – In the case of broken or unimplemented fee reading methods in the smart contract

Return type

eth_defi.vault.fee.FeeData

get_fee_mode()

Get how this vault accounts its fees.

Return type

Optional[eth_defi.vault.fee.VaultFeeMode]

get_flags()

Get various vault state flags from the smart contract.

Returns

Flag set.

Do not modify in place.

Return type

set[eth_defi.vault.flag.VaultFlag]

abstract get_flow_manager()

Get flow manager to read indiviaul settle events.

Return type

eth_defi.vault.base.VaultFlowManager

abstract get_historical_reader(stateful)

Get share price reader to fetch historical returns.

Parameters

stateful (bool) – If True, use a stateful reading strategy.

Returns

None if unsupported

Return type

eth_defi.vault.base.VaultHistoricalReader

Get a link to the vault dashboard on its native site.

  • By default, give RouteScan link

Parameters

referral (Optional[str]) – Optional referral code to append to the URL.

Returns

URL string

Return type

str

get_management_fee(block_identifier)

Get the current management fee as a percent.

Internal: Use get_fee_data().

Returns

0.1 = 10%

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) –

Return type

float

get_notes()

Get a human readable message if we know somethign special is going on with this vault.

Return type

Optional[str]

get_performance_fee(block_identifier)

Get the current performance fee as a percent.

Internal: Use get_fee_data().

Returns

0.1 = 10%

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) –

Return type

float

get_protocol_name()

Return the name of the vault protocol.

Return type

str

get_risk()

Get risk profile of this vault.

Return type

Optional[eth_defi.vault.risk.VaultTechnicalRisk]

get_withdraw_fee(block_identifier)

Withdraw fee is set to zero by default as vaults usually do not have withdraw fees.

Internal: Use get_fee_data().

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) –

Return type

float

abstract has_block_range_event_support()

Does this vault support block range-based event queries for deposits and redemptions.

  • If not we use chain balance polling-based approach

Return type

bool

has_custom_fees()

Does this vault have custom fee structure reading methods.

Causes risk in the vault comparison.

E.g.

  • Withdraw fee

  • Deposit fee

Returns

True if custom fee reading methods are implemented

Return type

bool

abstract has_deposit_distribution_to_all_positions()

Deposits go automatically to all open positions.

  • Deposits do not land into the vault as cash

  • Instead, smart contracts automatically increase all open positions

  • The behaviour of Velvet Capital

Return type

bool

property info: eth_defi.vault.base.VaultInfo

Get info dictionary related to this vault deployment.

  • Get cached data on the various vault parameters

Returns

Vault protocol specific information dictionary

abstract property name: str

Vault name.

property share_token: eth_defi.token.TokenDetails

ERC-20 that presents vault shares.

  • User gets shares on deposit and burns them on redemption

property short_description: Optional[str]

One-liner vault summary.

  • Shorter version of description() suitable for listings and tables

  • Returns None if not available

  • Override in subclasses that support offchain metadata

abstract property symbol: str

Vault share token symbol

class VaultFlowManager

Bases: abc.ABC

Manage deposit/redemption events.

  • For some vault structures, we need to know how much redemptions there are in the queue, so we can rebalance to have enough cash

  • Create a replay of flow events that happened for a vault within a specific block range

  • Not implemented yet

abstract fetch_pending_deposit(block_identifier)

Get how much users want to redeem from the vault.

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) – Block number

Returns

Number of underlying tokens the users want to redeem from the vault.

Return type

decimal.Decimal

abstract fetch_pending_deposit_events(range)

Read incoming pending deposits.

Parameters

range (Tuple[eth_typing.evm.BlockNumber, eth_typing.evm.BlockNumber]) –

Return type

None

abstract fetch_pending_redemption(block_identifier)

Get how much users want to redeem from the vault.

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) – Block number

Returns

Number of share tokens the users want to redeem from the vault.

Shares must be valued separately.

Return type

decimal.Decimal

abstract fetch_pending_redemption_event(range)

Read outgoing pending withdraws.

Parameters

range (Tuple[eth_typing.evm.BlockNumber, eth_typing.evm.BlockNumber]) –

Return type

None

abstract fetch_processed_deposit_event(range)

Read incoming pending deposits.

Parameters

range (Tuple[eth_typing.evm.BlockNumber, eth_typing.evm.BlockNumber]) –

Return type

None

abstract fetch_processed_redemption_event(vault, range)

Read outgoing pending withdraws.

Parameters
Return type

None

class VaultHistoricalRead

Bases: object

Vault share price and fee structure at the point of time.

__init__(vault, block_number, timestamp, share_price, total_assets, total_supply, performance_fee, management_fee, errors, vault_poll_frequency=None, max_deposit=None, max_redeem=None, deposits_open=None, redemption_open=None, trading=None, available_liquidity=None, utilisation=None)
Parameters
Return type

None

export()

Convert historical read for a Parquet/DataFrame export.

The returned dict conforms to RawVaultPriceRow.

Return type

eth_defi.vault.base.RawVaultPriceRow

is_almost_equal(other, epsilon=0.001)

Check if the read statistics match.

  • Throttle with epsilon relative difference to get rid of small increment rows

Parameters
Return type

bool

static migrate_parquet_schema(existing_table)

Migrate an existing Parquet table to the current schema.

When new columns are added to to_pyarrow_schema(), existing parquet files still have the old schema. This function adds missing columns as null arrays so incremental scans can write new data without losing columns.

Non-canonical columns (e.g. account_pnl, leader_fraction added by native protocol merges) are preserved so that the EVM scanner does not destroy data written by Hyperliquid/GRVT/Lighter.

Parameters

existing_table (pyarrow.Table) – Table read from an older parquet file.

Returns

Table with all canonical columns present (missing ones filled with nulls), legacy columns removed, extra columns preserved.

Return type

pyarrow.Table

classmethod to_pyarrow_schema()

Get parquet schema for writing this data.

  • Write multiple chains, multiple vaults, to a single Parquet file

  • Column semantics are documented in RawVaultPriceRow

Return type

pyarrow.Schema

static write_uncleaned_parquet(df, path, compression='zstd')

Write a DataFrame to the uncleaned parquet using proper PyArrow types.

Native protocol merge functions (Hyperliquid, GRVT, Lighter) must use this instead of pandas.DataFrame.to_parquet() to avoid type promotion (e.g. timestamp[ms]timestamp[us]) that breaks migrate_parquet_schema() on the next EVM scan run.

Columns present in the canonical schema are cast to their canonical types. Extra columns (from native protocols) are kept with their pandas-inferred types. No pandas index is written.

Parameters
  • df (pandas.DataFrame) – Combined DataFrame to write.

  • path (pathlib.Path) – Output parquet file path.

  • compression (str) – Parquet compression codec.

class VaultHistoricalReader

Bases: abc.ABC

Support reading historical vault share prices.

  • Allows to construct historical returns

__init__(vault)
Parameters

vault (eth_defi.vault.base.VaultBase) –

abstract construct_multicalls()

Create smart contract calls needed to read the historical state of this vault.

  • Multicall machinery will call these calls at a specific block and report back to process_result()

Return type

Iterable[eth_defi.event_reader.multicall_batcher.EncodedCall]

abstract process_result(block_number, timestamp, call_results)

Process the result of mult

Parameters
Return type

eth_defi.vault.base.VaultHistoricalRead

class VaultInfo

Bases: TypedDict

Vault-protocol specific intormation about the vault.

  • A dictionary of data we gathered about the vault deployment, like various smart contracts associated with the vault

  • Not standardised yet

__init__(*args, **kwargs)
__new__(**kwargs)
clear()

Remove all items from the dict.

copy()

Return a shallow copy of the dict.

fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items()

Return a set-like object providing a view on the dict’s items.

keys()

Return a set-like object providing a view on the dict’s keys.

pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from mapping/iterable E and F.

If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values()

Return an object providing a view on the dict’s values.

class VaultPortfolio

Bases: object

Track assets and balances in a vault.

  • Offchain method to track what assets a vault contains

  • Takes TradingUniverse as an input and resolves all relevant balances the vault holds for this trading universe

  • Because of brainrotten and awful ERC-20 token standard, the vault does not know what tokens it owns and this needs to be specific offchain

  • See VaultBase.fetch_portfolio()

__init__(spot_erc20, dex_hints=<factory>)
Parameters
Return type

None

get_raw_spot_balances(web3)

Convert spot balances to raw token balances

Parameters

web3 (web3.main.Web3) –

Return type

eth_defi.vault.lower_case_dict.LowercaseDict

is_spot_only()

Do we have only ERC-20 hold positions in this portfolio

Return type

bool

property tokens: set[eth_typing.evm.HexAddress]

Get list of tokens held in this portfolio

class VaultReadCondition

Bases: object

VaultReadCondition()

__init__()
class VaultSpec

Bases: object

Unique id for a vault.

  • Each vault can be identified by smart contract address by one of the contracts, related to its deployment. Usually this contract is vault contract itself.

  • We need both chain and address to specify vault we mean.

__init__(chain_id, vault_address)
Parameters
Return type

None

static parse_string(spec, separator='auto')

Parse vault spec from a string.

Parameters
  • spec (str) – String in the format of “chain_id,address” or “chain_id-address”

  • separator – Either “auto” or “-” or “,”

Returns

VaultSpec instance

Return type

eth_defi.vault.base.VaultSpec

verify_parquet_file(path, expected_rows=None, expected_schema=None, required_columns=None)

Read back a parquet file after writing and verify its integrity.

Performs a metadata read-back (not a full table load) to check:

  1. The file can be opened and its metadata read without errors

  2. Row count matches expected_rows if provided

  3. All columns in expected_schema are present with correct types (extra columns are permitted — e.g. native protocol columns)

  4. All required_columns are present

Uses pq.read_metadata() and pq.read_schema() instead of pq.read_table() to avoid loading the full dataset into memory.

This function should be called on a temp file before the atomic replace so that the previous good file is preserved when verification fails.

Parameters
  • path (Union[pathlib.Path, str]) – Path to the parquet file to verify.

  • expected_rows (Optional[int]) – If set, assert the file contains exactly this many rows.

  • expected_schema (pyarrow.Schema | None) – If set, verify that all columns in this schema are present with the correct types. Extra columns are permitted.

  • required_columns (Optional[list[str]]) – If set, verify these column names are present.

Returns

Verification result with metadata about the file.

Raises

ParquetVerificationError – If any verification check fails or the file cannot be read.

Return type

eth_defi.vault.base.ParquetVerificationResult