Enzyme vaults
Enzyme provides two distinct EVM vault architectures: Enzyme Blue and Enzyme Onyx. Both issue ERC-20 investor shares, but neither should be assumed to implement ERC-4626.
Enzyme blue
Enzyme Blue is the
established Enzyme architecture. A fund consists of a VaultProxy share
token and a paired ComptrollerProxy that controls fund accounting,
assets and investor actions. The existing eth_defi.enzyme.vault helpers
support this two-contract model for reading and managing funds. The scanner
discovers every reviewed Blue vault on Ethereum, Polygon, Base and Arbitrum from the
persistent Dispatcher VaultProxyDeployed event. It records the canonical
VaultProxy together with its ComptrollerProxy accessor, reads current GAV,
share supply and fees through a direct VaultBase adapter, and backfills GAV,
TVL and gross share price using batched historical Multicall reads.
Blue and Onyx factory detections retain a zero ERC-4626 deposit count because
their investor actions use protocol-specific events. Their feature flags bypass
the generic activity threshold so recurring scans continue both histories.
The historical Blue reader derives gross share value from GAV and share supply.
The future exact source is Enzyme’s release-aware
FundValueCalculatorRouter.calcNetShareValue(),
called through eth_call so fee settlement is simulated but not persisted.
It requires a third call per vault and sampled block. Until that is implemented,
the frontend fee-fill step will subtract the current exported fees to estimate
net value. Historical fee rates and performance-fee state can change, so this
is not an exact historical net share value.
Enzyme onyx
Enzyme Onyx is a separate, modular architecture. The current scanner support targets Onyx SharesFactory deployments on Base, where a vault’s standalone Shares token represents an investor’s interest in the vehicle.
The Shares contract is an ERC-20 token rather than an ERC-4626 vault. The
adapter therefore reads it directly through eth_defi.vault.base.VaultBase.
The scanner discovers Base Onyx vaults from the official SharesFactory’s
ProxyDeployed events and reads their stored share price and share supply.
It calculates total value as share price × share supply in the declared
value asset. A value asset can be a named unit, such as USD, rather than an
ERC-20 token, so this value is not necessarily a US-dollar valuation.
For comparable scanner metrics, each currently discovered Base Onyx named value
asset is exported with a canonical Base ERC-20 denomination: USD with USDC,
BTC with cbBTC, and EUR with EURC. This is a reporting convention for
the share price and TVL, not a claim that the corresponding token is accepted
by the current deposit handler: an USD-valued Onyx handler can instead accept
USDT or another ERC-20. Transaction integrations must inspect the active
handler rather than use this normalised denomination. A new, unsupported named
value asset fails before historical scanning instead of producing rows without
a denomination token.
The Onyx adapter supports discovery, metadata and historical accounting. It does not implement deposits, redemptions, flow-event accounting or portfolio composition, because these depend on the particular handler configuration.
The scanner records feature flags separately: enzyme_onyx_like marks
official Onyx SharesFactory deployments, while enzyme_blue_like is the
distinct label for the Blue VaultProxy/ComptrollerProxy model, including its
separate Base Dispatcher. The old
persisted enzyme_like feature value is retained as an Onyx compatibility
alias.
Every exported vault link points directly to that vehicle’s Enzyme application page using its canonical VaultProxy or Shares address and deployment network. Protocol-level metadata and the links section below may still use the generic discovery catalogue because they do not identify an individual vault.
Listing metadata
Enzyme share-token contracts provide a vault name but not a manager-entered
strategy narrative. Blue’s vault-detail app currently reads profiles through
an undocumented, unauthenticated GraphQL vaultProfile query. It supplies
the tagline and description used by the catalogue, plus manager
biography and public contact channels. This reader is not a stable public API
contract and must be reviewed when the app changes.
The scanner does not call the app for each vault during a chain scan. The
metadata migration gathers all existing Blue rows through small serial GraphQL
batches and writes a versioned JSON cache under
~/.tradingstrategy/cache/enzyme/vault-metadata.json. Blue adapters read
that cache without contacting the app, so a temporary app failure cannot cause
a database-wide description rewrite. An empty successful profile is evidence
that the manager supplied no public copy, not a failed lookup.
The scanner remains compatible with the previous cache schema during an upgrade, preserving existing Blue descriptions until the offchain migration completes. That migration ignores the older schema and refreshes every Blue profile before publishing contact fields in cache version two.
Onyx supports manager-editable vault and collection taglines and descriptions
in its management application, but Enzyme does not document a public API for
this data. The scanner therefore does not scrape a gated interface or consume
an undocumented endpoint. Every refreshed Onyx row has an empty short
description and the exact long-description marker Description is not
publicly available; a dedicated migration marker refreshes older generic
copy once. Blue leaves each description field empty when its app profile has
no manager text.
Deposit permission and availability
Current vault metadata exports the shared deposit_permission status. For
Blue, the direct adapter reads the active PolicyManager contracts and reports
whitelisted when the reviewed ALLOWED_DEPOSIT_RECIPIENTS policy is
enabled. This policy limits the wallets that may invest. No such policy means
permissionless; this classification does not promise that a deposit will
succeed, because approvals and other fund policies still apply. Asset,
adapter, transfer-recipient, deposit-size and redemption policies do not turn
the deposit-permission enum into whitelisted. The accompanying
whitelist.notes value makes clear that this is an address-level
restriction and does not establish an offchain KYC process.
The classification is per vault, not per Enzyme product family. Enzyme’s
Blue and Onyx comparison describes
Blue access as public while also listing whitelisting and deposit control for
both products. In practice, Blue is permissionless unless its manager enables
the Allowed Deposit Recipients policy.
The official Blue GetVaultConfiguration API exposes the same enabled
policy for an independent current-state comparison.
Onyx Shares does not enumerate its active deposit handlers. The Enzyme
migration therefore collects DepositHandlerAdded and
DepositHandlerRemoved alongside factory events in the same chain-level
Hypersync stream. It then inspects all active handlers at the fixed metadata
block using one batched Multicall read. A SyncDepositHandler is
whitelisted when getDepositorAllowlist() is non-zero; an
ERC7540LikeDepositQueue is whitelisted when
getDepositRestriction() selects its internal or external controller
allowlist. SharesMintHandler is also permissioned because only the owner or
an admin can select recipients for subscriptions settled offchain.
The migration persists that active handler set on each Onyx discovery lead. Later all-chain scanner cycles include the same add/remove topics in their normal incremental Hypersync stream and repeat only the batched current-state handler read. They therefore refresh current permission without rescanning historical blocks or falling back to per-vault event or RPC calls.
Issuance hooks can run arbitrary policies, including Chainlink ACE. A handler
with no built-in allowlist is therefore public only when its relevant pre- and
post-issuance hooks are also absent. The aggregation rule is: a proven public
active route makes the vault permissionless; all active routes requiring
prior account approval make it whitelisted; and a vault with no active
handler is permissionless for identity policy but has no route that can
currently accept deposits. An
unrecognised active handler or opaque hook remains unknown. Share-transfer
validators are deliberately excluded because they validate secondary
transfers, not minting. Likewise, an administrator’s ability to choose which
queued requests to settle is an operating condition rather than an address
whitelist. The
Onyx Shares and components documentation
defines deposit handlers as the accounts authorised to perform deposit actions,
while the subscription control documentation
describes the optional wallet allowlist. Neither is a substitute for reading
each vault’s current handler configuration.
Neither architecture exports historical deposits_open or
redemption_open values. Blue policies and Onyx handlers are mutable and
can depend on the particular investor and action, so past GAV, share price and
supply samples cannot demonstrate universal availability. These historical
columns intentionally remain null rather than reporting a misleading state.
Fees
The Onyx adapter reads current fees selected per vehicle;
it reads active management and performance fee trackers, plus the current
standard FeeHandler entrance and exit rates, during a current metadata scan.
Management and performance rates are annual fractions; entrance and exit rates
reduce the investor’s issued or redeemed shares. The standard FeeHandler has no
separate global protocol-fee setting, so the exported Onyx management fee is
already the full user-facing recurring charge and does not double-count a
platform charge. A configured zero address for the FeeHandler or one of its
trackers proves that the corresponding fee is disabled, so the adapter exports
0.0 rather than an unknown value.
Historical fee rates are not yet exported, because the fee handler or tracker
can change over a vault’s lifetime; that backfill needs component-change,
RateSet, EntranceFeeSet and ExitFeeSet event handling.
Blue has a different fee model. Its fund-level FeeManager configuration can be
combined with an additional protocol fee. Enzyme’s Protocol Fees
documentation defines this as a fee on Assets Under Technology, settled by
share inflation or MLN payment, rather than a high-water-mark performance fee.
The current export therefore includes the ProtocolFeeTracker rate in the
user-facing management fee. It also retains the protocol rate separately, so
consumers can calculate the manager-only rate as Mgmt fee - Protocol fee.
Blue management, performance, entrance and exit rates are current reads only.
FeeManager is the authoritative enumeration of Blue plugins: when it omits a
standard fee plugin, that fee is exported as 0.0. Across both Enzyme
architectures, null is reserved for an unavailable or inconclusive read,
never for a fee that the current configuration has proved disabled. This keeps
the investor-facing fee schedule complete whenever Enzyme makes it available.
Historical Blue fee configuration remains TODO because releases, FeeManager
plugins and protocol-fee trackers can change at migration boundaries.
Permission audit report
scripts/enzyme/report-whitelist.py reads the persisted metadata used by
the website and groups all Enzyme rows by chain, Blue/Onyx architecture and
whitelisted/permissionless/unknown status. It does not mutate the
database. Set DETAILS=true to print every vault. To compare Blue rows with
Enzyme’s authenticated vault configuration API, set
CHECK_ENZYME_API=true and ENZYME_API_TOKEN; MAX_WORKERS controls
the bounded threaded API reads. Onyx is excluded from this API comparison
because its authoritative permission state belongs to the active handler set.
The Enzyme migration refreshes a healthy Blue or Onyx row whose persisted
permission is missing or unknown. The Onyx-specific migration marker also
forces one refresh of previously labelled rows when handler-classification
semantics change. An inconclusive custom handler is not retried repeatedly at
the same checkpoint, but a later completed run evaluates it at the newer fixed
block.
poetry run python scripts/enzyme/report-whitelist.py
CHECK_ENZYME_API=true ENZYME_API_TOKEN="$ENZYME_API_TOKEN" \
poetry run python scripts/enzyme/report-whitelist.py
ENZYME_SCAN_PRICES=false \
poetry run python scripts/enzyme/backfill-history.py
Current metadata migration
scripts/enzyme/migrate-current-metadata.py is the safe production entry
point for applying cached optional Blue descriptions,
the explicit Onyx unavailable marker, direct address-specific links and current
Blue and Onyx permission data to existing Enzyme rows. It reuses the
targeted factory discovery and durable metadata batching while forcibly
disabling historical price and cleaned-Parquet writes. Factory and Onyx handler
events use the same per-chain Hypersync stream, and all active Onyx handlers
use one current-state Multicall pass on Base. Successfully migrated rows become
their own resume markers, so an interrupted rerun skips them without a blanket
metadata refresh or duplicate RPC calls. The metadata-only checkpoint is
separate from the historical-price checkpoint, and the command holds the
shared scanner writer lock while the metadata pickle is loaded and replaced.
Blue permission reads cover Sulu and the deprecated Encore and Phoenix policy
managers and whitelist identifiers used by the Enzyme website. Current NAV and
fee fields remain blank when a deprecated vault can no longer execute its old
release calls; name, symbol, denomination and the architecture-specific
description policy remain mandatory. Blue app-profile fields are optional, whereas
Onyx must have an empty short description and the explicit unavailable
long-description marker.
DRY_RUN=true poetry run python scripts/enzyme/migrate-current-metadata.py
MAX_WORKERS=8 poetry run python scripts/enzyme/migrate-current-metadata.py
Offchain description migration
scripts/enzyme/migrate-offchain-metadata.py snapshots the app-profile
fields for every discovered Blue vault, warms the durable adapter cache and
updates the public metadata pickle. It retains a vault tagline and description,
manager biography, free-form contact information, email, Telegram, X/Twitter
and website. The manager identifier uses Twitter, Telegram or a non-generic
email local part; where none exists, it falls back to the website domain.
The migration is separate from scanning: it has no JSON-RPC or Hypersync work,
does not change historical prices, and does not query Onyx. It starts in
dry-run mode. An app-profile failure prevents publication. A real run creates a
timestamped database backup, writes completed profile batches to
enzyme-offchain-metadata-state.json, then removes the checkpoint only after
both the cache and database write succeed. Set ENZYME_METADATA_STATE_PATH
to override its location.
Every discovered Blue vault is collected regardless of NAV or denomination.
The migration also clears the exact retired generated Blue fallback fields
locally for every Blue row, ensuring older rows do not retain invented copy.
After a complete cache/database update, later runs reuse the cache. Set
ENZYME_METADATA_REFRESH=true to fetch all Blue rows again.
ENZYME_REQUEST_INTERVAL_SECONDS defaults to one second between request
batches to avoid backend and Cloudflare rate limits during a full refresh.
Requests are strictly serial and each batch contains at most five aliases,
which is the current app-enforced limit.
Run the migration as follows:
source .local-test.env
poetry run python scripts/enzyme/migrate-offchain-metadata.py
DRY_RUN=false poetry run python scripts/enzyme/migrate-offchain-metadata.py
For production, first stop vault-scanner-looped and run this command in
the one-shot container described above, so the cache and metadata database are
the same mounted persistent state used by the scanner. Restart the looped
scanner after the migration completes.
For production maintenance, first inspect docker-compose.yml and stop the
looped scanner. Run the migration through the one-shot service so it uses the
mounted production state, then restart the looped service:
source ~/vault-scanner/vault-rpc.env
cd ~/vault-scanner/web3-ethereum-defi
docker compose stop vault-scanner-looped
docker compose run --rm --entrypoint /bin/bash \
vault-scanner-oneshot \
-c 'poetry run python scripts/enzyme/migrate-offchain-metadata.py'
docker compose start vault-scanner-looped
Vault export
scripts/enzyme/export-vaults.py prints the local Enzyme database as a
Markdown table, including its stored short-description column. It is read-only
and never contacts Enzyme, JSON-RPC or Hypersync. Its
Total value (accounting unit) column
is the vault’s accounting-unit NAV, not USD-normalised TVL, so rows with
different denominations must not be ranked together. To review the twenty
largest rows among selected USD-pegged accounting units, run:
VALUE_UNITS=USDC,DAI,USDT SORT_BY_TOTAL_VALUE=true LIMIT=20 \
poetry run python scripts/enzyme/export-vaults.py
VALUE_UNITS is a case-insensitive comma-separated filter,
SORT_BY_TOTAL_VALUE=true sorts the selected records descending by their
reported NAV, and LIMIT caps the output. The command does not infer a
live USD exchange rate or guarantee that a pegged asset is worth one dollar.