# Superform Documentation
> All documentation pages in navigation order. See https://docs.superform.xyz/llms.txt for the structured index.
# What is Superform?
Source: https://docs.superform.xyz/index
## Do less. Earn more.
Protocol architecture, smart accounts, hooks, and the execution model.
Integrate SuperVaults, write hooks, or become a validator.
Full technical reference for curators and operators.
Contract addresses across all supported chains.
---
# Superform Protocol
Source: https://docs.superform.xyz/learn/learn
The Superform Protocol consists of [Superform Core](https://github.com/superform-xyz/v2-core) and [Superform Periphery](https://github.com/superform-xyz/v2-periphery).
Superform Core is a suite of persistent, non-upgradeable contracts that compresses arbitrary actions into a single, Merkle-verified signature. Execution lives inside ERC-7579 Smart Accounts and composition comes from plug-and-play Hooks. Anyone can deploy Superform Core and add new hooks to the protocol.
On top of Superform Core, various structured products have been built on top, collectively referred to as Superform Periphery. SuperVaults are permissionlessly createable, validator-secured vaults that can run arbitrary hooks while exposing deterministic price-per-share (PPS) and fee rules. SuperAssets are user-facing omnichain savings tokens (e.g., SuperUSD, SuperETH) that support decentralized allocation and incentivized rebalancing across many SuperVault positions.
### Why Superform v2?
- **One-signature UX verifiable onchain:** Hook bundles turn bridge, swap, lend, and deposit into one click regardless of the number of chains without usage of offchain APIs.
- **Validator-secured vaults:** Validators attest to SuperVault price-per-share, with quorum, signature, and strategy-level safety checks minimizing the risk surface around centralized strategists.
- **Omnichain liquidity:** SuperAssets stream yield from any chain back to Ethereum L1.
## Technical Resources
- [Whitepaper](https://github.com/superform-xyz/whitepapers/blob/main/superform_v2_whitepaper.pdf) - Superform v2 published June 2025
- [v2-core GitHub](https://github.com/superform-xyz/v2-core) - Core smart contracts and diagrams
- [v2-periphery GitHub](https://github.com/superform-xyz/v2-periphery) - Periphery smart contracts and diagrams
- [Integrate SuperVaults](/build/integrate-supervaults) - REST API + Bundler guide for wallets, custodians, and apps
---
# Superform Core
Source: https://docs.superform.xyz/learn/superform-core
Superform Core is a suite of persistent, non-upgradeable contracts that compresses arbitrary actions into a single, Merkle-verified signature. Execution lives inside ERC-7579 Smart Accounts and composition comes from plug-and-play Hooks. Anyone can deploy Superform Core and add new hooks to the protocol.
Core consists of the following components:
- **Execution Layer**: ERC-7579 executors (SuperExecutor, SuperDestinationExecutor) that process hook bundles with transient storage for gas-efficient inter-hook communication.
- **Validation Layer**: Merkle-proof validators (SuperValidator, SuperDestinationValidator) enabling single-signature authorization for batched multi-chain operations.
- **Accounting Layer**: SuperLedger and YieldSourceOracles for trustless cost basis tracking and performance fee calculation across vault standards (ERC-4626, ERC-5115, ERC-7540, Pendle).
- **Infrastructure**: Bridge adapters for cross-chain messaging, SuperNativePaymaster for ERC-20 gas sponsorship, and SuperBundler for batched UserOperation processing.
## Hooks
Hooks are lightweight, permissionless contracts which perform a specific action: bridge, swap, lend, and more. They are designed to be composable and can be chained together to create complex transaction flows which can achieve any multichain strategy.
See [Hooks](/learn/hooks) for a full overview of Hook types, lifecycle, and classifications.
---
# Smart Accounts
Source: https://docs.superform.xyz/learn/smart-accounts
Superform routes every interaction through lightweight ERC-7579 Smart Accounts. They add gas abstraction, passkey or social-recovery login, and session keys, while remaining fully self-custodial. All accounts created through Superform currently use [Biconomy Nexus v1.2.0](https://github.com/bcnmy/nexus/releases/tag/v1.2.0).
## Modules
Smart accounts access Superform by installing four ERC-7579 modules:
| Module | Purpose |
|--------|---------|
| SuperExecutor | Executes same-chain ordered Hook bundles atomically |
| SuperDestinationExecutor | Executes cross-chain ordered Hook bundles |
| SuperValidator | Validates same-chain Merkle proofs and bundled signatures (EIP-1271 compliant) |
| SuperDestinationValidator | Validates cross-chain Merkle proofs and bundled signatures (not EIP-1271 compliant; do not use for EntryPoint calls) |
EOA users can opt to only passthrough these smart accounts via EIP-7702 if they do not wish to store funds on the smart account.
---
# Hooks
Source: https://docs.superform.xyz/learn/hooks
Hooks are lightweight, permissionless contracts which perform a specific action: bridge, swap, lend, and more. They are designed to be composable and can be chained together to create complex transaction flows which can achieve any multichain strategy. Hook chains can be fulfilled by any ERC-7579 smart account with the SuperValidator and SuperExecutor modules installed.
If you are interested in building hooks, jump straight to [Write a Hook](/build/hooks).
## Key Concepts
Hooks implement transient storage during the execution of a transaction to temporarily hold state changes. This avoids the gas costs of permanent storage writes between hooks. If any hook in a chain fails, the entire transaction is reverted, ensuring atomicity.
### Hook Lifecycle
The execution of hooks is a four-step sequence:
1. **Build** - Frontend selects the addresses and calldata of the hook(s) in a chain.
2. **Bundle** - SuperBundler splits hooks per chain and wraps each into an ERC-4337 UserOp, storing a Merkle tree.
3. **Validate** - Smart Account delegates to SuperValidator onchain to perform Merkle proof verification.
4. **Execute** - SuperExecutor iterates through hooks, performs each action and updates SuperLedger.
### Hook Classifications
Hooks are classified based on `HookType` and `HookSubType`.
`HookType` must be defined as `NONACCOUNTING`, `INFLOW`, or `OUTFLOW` depending on the hook's expected interaction with the accounting system.
`HookSubTypes` designate which action category the hook implements.
- **NONACCOUNTING** - Utility hooks that do not update the accounting system. These hooks include `HookSubTypes` like `SWAP`, `STAKE`, `BRIDGE`.
- **INFLOW** - Hooks that process additions to positions and typically encode some sort of `deposit` action. They include a `HookSubType` corresponding to the underlying vault being deposited into like `ERC4626`, `ERC7540`, etc.
- **OUTFLOW** - Hooks that process reductions to positions and typically encode some sort of `redeem` action. They include similar `HookSubType` to `INFLOW` hooks.
---
# SuperVaults
Source: https://docs.superform.xyz/learn/supervaults
SuperVaults are ERC-7540 tokenized vaults that let operators manage yield strategy execution through protocol-bounded roles. Depositors supply assets to vault contracts; operators configure and execute approved strategy actions within Merkle, PPS, fee, slippage, timelock, and accounting constraints.
## Key Properties
- **Non-custodial** — Depositor funds live in vault contracts, not operator-controlled wallets
- **ERC-4626 compatible** — Standard deposit/withdraw interface with async ERC-7540 extensions for cross-chain and illiquid positions
- **Merkle-verified execution** — Keepers can only call hooks that match leaves in the onchain Merkle tree
- **Automated operations** — Workers handle any actions, from deposits, redemptions, reward claims, swaps, and transaction management
- **Oracle-secured PPS** — Price-per-share is computed by validators, signed as EIP-712 typed data, and accepted onchain only after two-thirds weighted consensus
## How SuperVaults Work
A depositor submits a deposit or redemption request onchain via the ERC-7540 interface.
Seven automated workers process requests: allocating deposits to configured yield sources, fulfilling redemptions, claiming rewards, and executing swaps.
The operator sets allocation priorities, defines automated rebalancing rules, configures Merkle authorization for keeper operations, and monitors vault health within protocol-enforced constraints.
Off-chain validators compute price-per-share, sign EIP-712 attestations, and anyone can submit the update onchain. The oracle accepts updates only with supermajority validator consensus.
## Security Model
SuperVaults enforce safety at every layer:
- **Double-Merkle hook allowlisting and blacklisting** at both global and strategy levels ensures vaults can only perform pre-approved actions like depositing, redeeming, swapping, and bridging. Hook-root updates are proposal based, with the aggregator hook-root timelock defaulting to 15 minutes, and keepers still cannot execute anything outside approved leaves.
- **Clear role separation** across Managers, Validators, and Guardians. Managers operate through pre-approved hooks, validators attest price-per-share, and guardians retain emergency intervention powers such as pause controls.
- **Validator-signed price-per-share (PPS)** with quorum, signature, staleness, and deviation checks enforced onchain.
- **Timelocked sensitive parameters** such as fee changes, PPS expiration threshold updates, active PPS oracle changes after first set, and some governance actions, while other operational controls remain immediate.
- **Automated emergency controls** provide pause functionality, banned leaves, and emergency withdrawal paths using authorized hooks.
Managers cannot execute arbitrary calldata or withdraw depositor assets to arbitrary destinations. Secondary managers, session keys, and keepers are limited to role-authorized actions and pre-authorized Merkle leaves. Emergency withdrawal hooks are de-risking paths that move positions back through strategy/vault accounting, and redemption fulfillment remains bounded by ERC-7540 request state, PPS, slippage expectations, fee logic, and accounting updates.
## Transparency
Depositors do not have to trust; they can verify:
- A **live transparency dashboard** surfaces allocations, caps, validator signatures, and performance in real time.
- **PPS can be recomputed by anyone**, onchain or offchain, with circuit breakers that pause abnormal behavior automatically.
- All **underlying protocols can continuously be monitored** for exploit vectors and governance risk, and quickly allocated out of if needed.
## Efficiency
SuperVaults are engineered for scale:
- **Native ERC-7540 compliance** enables synchronous deposits and async redemptions with onchain composability.
- **Cross-chain deposits** via the SuperBundler support 8\+ networks with a single signature.
- **Modular hook engine** allows SuperVaults to access any yield source. Strategies can evolve as markets do, without redeployments or protocol upgrades.
## Architecture

The architecture comprises three systems: vault triads, governance coordination, and PPS validation.
### Vault Triads
Each SuperVault deployment consists of three onchain contracts managed through the **SuperVaultAggregator** (factory, registry, PPS oracle forwarding, upkeep accounting, and governor-level constraints):
| Contract | Purpose |
| --- | --- |
| **SuperVault** | ERC-7540 vault that holds depositor assets and issues share tokens. Entry-point for synchronous deposits and asynchronous redeems. |
| **SuperVaultStrategy** | Execution engine that manages hook authorization via dual Merkle roots, tracks PPS high water mark, queues/fulfills redemption requests, and enforces fee policies. |
| **Escrow** | Holds user shares during pending redemptions (allowing cancellation) and assets during fulfillment claims. |
### Governance & Coordination
| Component | Role |
| --- | --- |
| **\$UP Token** | Protocol token used for governance via sUP, funds SuperVault upkeep for PPS update writes, and gains validator-bonding utility in later phases. |
| **SuperGovernor** | Central registry and access control hub. Manages role-based permissions, selected timelocked parameter updates, and emergency intervention powers. |
| **SuperBank** | Protocol treasury that handles protocol fees and executes governance-approved hook operations. |
### PPS Validation
| Component | Role |
| --- | --- |
| **ECDSAPPSOracle** | Validates price-per-share updates using ECDSA signatures and forwards validated updates to the Aggregator. |
| **Validator Network** | Validators monitor PPS updates and submit attestations, with consensus, signature, and quorum checks enforced before updates are accepted. |
## Supported Yield Sources
| Type | Description | Example |
| --- | --- | --- |
| `erc4626` | Standard ERC-4626 vaults | Morpho, Aave wrappers, Euler |
| `pendle_pt` | Pendle Principal Tokens | Fixed-rate yield via PT purchase |
ERC-7540 yield sources (async vaults) are on the roadmap.
## Live Deployments
SuperVaults are live on **Ethereum** and **Base** with flagship vaults for USDC, WETH, and WBTC. See [Deployment Addresses](/deployment-addresses) for the full contract list.
## Next Steps
End-to-end flow from deposit request to yield accrual.
Fee structure, \$UP upkeep, and protocol revenue.
Full technical guide for teams operating vaults.
---
# Transaction Lifecycle
Source: https://docs.superform.xyz/learn/supervaults/transaction-lifecycle
## Deposits
1. User Deposit: User calls `deposit()` with assets
2. Immediate Minting: Shares minted synchronously at current PPS
3. Asset Custody: Assets transferred to strategy for allocation
4. Hook Execution: Strategist executes hooks to deploy capital
5. PPS Updates: Validators provide updated PPS reflecting new positions
## PPS Updates
1. Off-Chain Calculation: Validators calculate PPS independently
2. Signature Generation: Validators sign PPS with private keys
3. Aggregation: Signatures collected and submitted to oracle
4. Validation: Oracle verifies signatures and quorum
5. Forwarding: Validated PPS forwarded to aggregator
6. Strategy Checks: Aggregator runs dispersion/deviation checks
7. Storage: PPS stored and made available for vault operations
## Strategy Management
1. Hook Approval: Strategist proposes new hook root
2. Timelock Period: 15-minute delay configurable by governance for strategy hooks
3. Execution: Hook root updated and new strategies enabled
4. Governance Oversight: Root updates remain visible during the timelock before they take effect
## Redemptions
1. Redeem Request: User calls `requestRedeem()` with shares
2. Redeem Slippage: User optionally calls `setRedeemSlippage()` (default 0.5%)
3. Share Escrow: Shares transferred to escrow contract
4. Fulfillment: Strategist executes hooks to free up assets. Hooks must be pre-authorized in the active Merkle root — see [Hooks](/learn/hooks).
5. PPS Validation: Slippage protection against recorded request PPS
6. Asset Claim: User calls `withdraw()` to receive assets
## PPS Validation
Flexibility and security is economically enforced by validators submitting PPS, supported by oracle infrastructure in Superform core. If interested in becoming a SuperVault validator, we recommend reading [Become a Validator](/build/become-a-validator).
Validators currently use a ECDSAPPSOracle which validates price updates using cryptographic signatures from the decentralized validator network.
1. Message Construction: `keccak256(abi.encodePacked(strategy, pps, ppsStdev, validatorSet, totalValidators, timestamp))`
2. Signature Recovery: Uses ECDSA to recover signer addresses
3. Validator Verification: Checks signers against registered validator set
4. Quorum Enforcement: Requires minimum number of signatures
5. Duplicate Prevention: Prevents the same validator from signing twice
---
# Economics
Source: https://docs.superform.xyz/learn/supervaults/economics
All fees taken are configurable by the strategist with a split to Superform that can be turned on by governance.
| Fee Type | Description |
| --- | --- |
| Management | Configurable by the strategist, applied to the initial deposit amount |
| Performance | Configurable by the strategist, applied to profits above cost basis when users redeem |
| Upkeep | Strategists pay UP tokens for validators to update prices |
### Fee Skimming Mechanism
Performance fees are taken only from realized profits, these fees are obtained periodically via the fee skimming mechanism.
The high-water mark is the last price-per-share level at which performance fees were taken; fees are only charged on PPS growth above this baseline, and after each fee skim the new (post-fee) PPS becomes the updated high-water mark for future calculations.
Because deposits and redemptions are PPS-neutral, the high-water mark only moves when yield has actually been realized and fees are skimmed on that realized profit.
---
# $UP
Source: https://docs.superform.xyz/learn/usdup
$UP is the token representing ownership over Superform. Staking it yields sUP, which confers voting power over SuperVault parameters, SuperAsset weights, economic configuration, and SuperVault upkeep participation. Later protocol phases introduce validator bonding and slashing utility for $UP.
Contract addresses for $UP and sUP across all supported chains are listed on the [Deployment Addresses](/deployment-addresses) page.
## Phases
| Phase | Milestone | New $UP utility |
| --- | --- | --- |
| 0 | Open Access | Initial set of protocol params |
| **1** | SuperVault launch | Validator-secured PPS and $UP-funded upkeep payments |
| 2 | Validator decentralization | Bond $UP to join validator sets with slashing |
| 3 | SuperAsset launch | Voting on SuperAsset composition |
| 4 | Bundler decentralization | Bundlers may bond $UP in a similar fashion to validators |
---
# Glossary
Source: https://docs.superform.xyz/learn/glossary
Terms specific to SuperVaults (v2). For legacy SuperAssets (v1) terms, see the [v1 documentation](https://github.com/superform-xyz/whitepapers).
---
- [**EIP-7702**](https://eip7702.io/) — Ethereum standard allowing EOAs to act as smart accounts, enabling existing wallets to use account-abstraction features without migration.
- [**ERC-4337**](https://www.erc4337.io/) — Account-abstraction standard that lets smart-contract wallets function like EOAs via a `UserOperation` mempool and bundler system.
- [**ERC-4626**](https://eips.ethereum.org/EIPS/eip-4626) — Tokenized vault standard defining a common deposit/withdraw interface. SuperVaults implement this for synchronous deposits.
- [**ERC-7540**](https://eips.ethereum.org/EIPS/eip-7540) — Async vault extension to ERC-4626. Defines `requestRedeem` / `requestDeposit` flows for cross-chain or illiquid positions. SuperVaults use ERC-7540 for redemptions.
- [**ERC-7579**](https://erc7579.com/) — Modular smart-account standard enabling plug-and-play functionality through a standardized module interface. SuperExecutor and SuperValidator are ERC-7579 modules.
- **ECDSAPPSOracle** — Onchain oracle contract that validates PPS updates submitted by validators. Checks EIP-712 signatures, enforces supermajority quorum (≥ 2/3 weighted stake), verifies nonce binding, and forwards accepted updates to the SuperVaultAggregator.
- **Escrow** — Immutable contract that holds user shares during pending redemptions (allowing cancellation) and holds assets during fulfillment until the user claims.
- **Hook** — Lightweight, ERC-7579-compliant module that performs a single action (lend, bridge, swap, etc.) inside a strategy. Hooks are composable and chainable. All hook executions are Merkle-gated — only pre-authorized hook + parameter combinations can execute.
- **Keeper** — Automated worker that executes authorized vault actions: allocating deposits, fulfilling redemptions, claiming rewards, swapping, bridging. Keepers operate within Merkle-authorized bounds.
- **Merkle Root** — Onchain commitment to the set of authorized hook + parameter combinations for a vault. No hook can execute unless its exact call matches a leaf in the active root. Updated via a timelocked proposal flow.
- **Price Per Share (PPS)** — The exchange rate between one vault share and the underlying asset. `PPS = totalAssets / totalSupply`. Increases as yield accrues; steps down at each performance fee-skim event. Net direction is upward when the vault generates yield. Signed by validators and accepted onchain after supermajority consensus.
- **Primary Manager** — Highest-authority operator for a SuperVault. Proposes Merkle roots, updates fees, manages secondary managers, and authorizes hook configurations. Actions remain bounded by protocol timelocks and accounting constraints.
- **Secondary Manager** — Delegated operator that can execute day-to-day vault operations (execute hooks, skim fees, whitelist yield sources) within primary-manager-authorized and Merkle-gated bounds.
- **Session Key** — Narrowly scoped automation credential that allows keepers to execute pre-authorized actions without requiring the manager's primary signing key for each operation.
- **SuperBundler** — Off-chain service that aggregates user actions into ERC-4337 UserOps, resolves hook sequences, and returns ready-to-broadcast calldata. Accessed via `https://bundler.superform.xyz/`.
- **SuperGovernor** — Onchain registry and access control hub. Manages role-based permissions, timelocked parameter updates, and emergency intervention powers across all SuperVaults.
- **SuperVault** — ERC-7540 vault contract that holds depositor assets and issues share tokens. Entry point for deposits and redemption requests.
- **SuperVaultAggregator** — Factory and registry contract. Creates vault/strategy/escrow triads, records PPS attestations, manages upkeep accounting, and enforces governor-level constraints.
- **SuperVaultStrategy** — Execution engine paired with each SuperVault. Manages hook authorization via dual Merkle roots, tracks PPS high-water mark, queues and fulfills redemption requests, and enforces fee policies.
- **$UP / sUP** — `$UP` is the Superform Protocol utility token. Staking it yields `sUP`, which grants governance rights. `$UP` also funds vault upkeep (the gas cost of validator PPS writes) and gains validator-bonding utility in later protocol phases.
- **Upkeep** — `$UP` balance held by a vault strategy to fund PPS update gas costs. Vaults with zero upkeep stop receiving PPS updates and enter a degraded operating state. L1 vaults require at least 100,000 $UP; non-L1 vaults require at least 1,000 $UP.
- **Validator** — Off-chain node that independently computes PPS, signs an EIP-712 attestation, and submits it onchain. Updates are accepted only with supermajority (≥ 2/3 weighted) consensus across the active validator set.
- **Yield Source** — An external protocol (Morpho vault, Aave pool, Pendle PT, etc.) that a SuperVault strategy allocates to. Must be whitelisted by the primary manager and paired with a compatible oracle before keepers can interact with it.
---
# Links
Source: https://docs.superform.xyz/learn/links
## Source Code
| Repository | Description |
|-----------|-------------|
| [v2-core](https://github.com/superform-xyz/v2-core) | Core smart contracts: hooks, executors, validators, ledger |
| [v2-periphery](https://github.com/superform-xyz/v2-periphery) | Periphery contracts: SuperVaults, $UP, oracles |
| [Brand Kit](https://github.com/superform-xyz/brand-kit) | Logo pack and color palette |
## Papers
- [Superform v2 Whitepaper](https://github.com/superform-xyz/whitepapers/blob/main/superform_v2_whitepaper.pdf) - Published June 2025
## Standards
| Standard | Relevance |
|----------|-----------|
| [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) | Tokenized vault standard, base interface for yield sources |
| [ERC-7540](https://eips.ethereum.org/EIPS/eip-7540) | Asynchronous tokenized vault; SuperVault implements this for async deposits and redemptions |
| [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) | Minimal modular smart accounts; Superform's execution and validation modules |
| [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) | Account abstraction; SuperBundler packages hook chains into UserOperations |
| [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) | EOA passthrough for smart account delegation |
## Community & Links
### Sites
- [Superform](https://www.superform.xyz/) — main website
- [Foundation](https://www.superformfoundation.org/) — foundation website
- [Help Center](https://help.superform.xyz/) — general support
### Community
- [Twitter / X](https://x.com/superformxyz)
- [Discord](https://discord.gg/superform)
- [Mirror](https://mirror.xyz/superform.eth)
### Audit Reports
- [v2-core Audits](https://github.com/superform-xyz/v2-core/tree/dev/audits) — core smart contract audits
- [v2-periphery Audits](https://github.com/superform-xyz/v2-periphery/tree/dev/audits) — periphery smart contract audits
### Legacy
- [v1 Docs](https://superform.gitbook.io/superform-v1)
- [v1 App](https://legacy.superform.xyz/)
---
# Introduction
Source: https://docs.superform.xyz/build
Deposit and redeem SuperVaults from your wallet, custodian, or app.
Build composable contracts for bridge, swap, and lend actions.
Stake $UP, attest to price-per-share updates, and earn protocol rewards as part of the validator network.
---
# Integrate SuperVaults
Source: https://docs.superform.xyz/build/integrate-supervaults
An integration covers three areas:
1. **[Vault Data](/build/earn/vault-data)**: discover vaults, read APY and TVL, fetch user positions
2. **[Deposit](/build/earn/deposit)**: route user deposits via the Bundler or direct contract calls
3. **[Redemption](/build/earn/redemption)**: async ERC-7540 request and claim lifecycle
4. **[Rewards](/build/earn/rewards)**: display accruing Merkl rewards and submit claim transactions
## Prerequisites
| Item | Value |
|---|---|
| Persephone API | `https://persephone.superform.xyz/v1` |
| Bundler API | `https://bundler.superform.xyz/` |
| Auth | Both APIs are fully public, no API key required |
| Chain | Base (chain ID `8453`) |
| Client Library | [TypeScript + Python copy-paste clients](/build/sdk) |
## Wallet type
| Your wallet | Path |
|---|---|
| EOA, Safe, Fireblocks, custodian | `POST /hooks/suggest/non-smart-account` — returns ordered `executions[]` |
| ERC-4337 smart account | `POST /hooks/suggest` → `/executor/build` → `/executor/execute` |
All current SuperVaults use `vault_type: "SUPERVAULT"` (when `is_supervault_v_2: true`).
---
# Quickstart
Source: https://docs.superform.xyz/build/earn/quickstart
Fetch all available SuperVaults on Base:
```bash
curl https://persephone.superform.xyz/v1/supervaults?chain_id=8453
```
Filter to `availability: "available"` and `is_supervault_v_2: true`. Pick a vault address and note its `assets[0].address` (the token your user will deposit).
Example (NVDA SuperVault):
- Vault: `0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc`
- Underlying token (NVDAc): `0xb20000000000000000000078ee7ce2fe4908108c`
- Decimals: 8
The Bundler validates balance on-chain and returns 400 if the user's balance is insufficient. Check first:
```bash
curl https://persephone.superform.xyz/v1/balances/0xUSER_ADDRESS
```
Replace `100000000` with the actual token amount. All bStock tokens use 8 decimals (`100000000` = 1 token).
```bash
curl -X POST https://bundler.superform.xyz/hooks/suggest/non-smart-account \
-H "Content-Type: application/json" \
-d '{
"account": "0xUSER_WALLET_ADDRESS",
"action": {
"type": "DEPOSIT",
"slippage": 50,
"sources": [
{
"address": "0xb20000000000000000000078ee7ce2fe4908108c",
"chain_id": 8453,
"amount": "100000000"
}
],
"targets": [
{
"address": "0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
"chain_id": 8453,
"vault_type": "SUPERVAULT",
"amount_proportion": 1
}
],
"route_type": "OUTPUT",
"outputs": []
}
}'
```
The response is an ordered `executions[]` array.
Send each transaction in order. Wait for on-chain confirmation before sending the next.
```
executions[0] — ERC20.approve(vault, amount) selector: 0x095ea7b3
executions[1] — ERC4626.deposit(amount, user) selector: 0x6e553f65
```
Both carry `type: "DEPOSIT"`. Use array index, not type, to determine order.
After `executions[1]` confirms, the user holds `superNVDA` shares.
---
# Vault Data
Source: https://docs.superform.xyz/build/earn/vault-data
Base URL: `https://persephone.superform.xyz/v1`. No API key required for any read endpoint.
## List vaults
```bash
GET https://persephone.superform.xyz/v1/supervaults?chain_id=8453
```
**Key response fields**
| Field | Type | Description |
|---|---|---|
| `id` | string | Stable vault ID: `{chain_id}_{address}` |
| `address` | string | Vault contract address |
| `name` | string | Onchain name (e.g. `NVDA SuperVault`) |
| `symbol` | string | Share token symbol (e.g. `superNVDA`) |
| `decimals` | number | Share token decimals |
| `availability` | string | `available`, `withdraw_only`, `deposit_only`, `paused`, `unavailable` — always check before presenting UI |
| `is_supervault_v_2` | boolean | `true` for all current SuperVaults |
| `has_rewards` | boolean | `true` if active Merkl reward campaigns exist |
| `stats_basic.tvl_total` | number | TVL in USD |
| `stats_basic.apy_snapshot_week` | number | 7-day trailing strategy yield in percentage points. A value of `-1000000` means no data yet — treat as 0. |
| `stats_basic.reward_rate` | number | Merkl reward APR in percentage points. Sum with `apy_snapshot_week` for total APY. |
| `stats_basic.pps` | number | Price per share in underlying asset units |
| `stats_basic.pps_usd` | number | Price per share in USD |
| `assets[]` | array | Underlying assets: `address`, `symbol`, `decimals`, `price_usd` |
**Example response**
```json
{
"id": "8453_0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
"address": "0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
"chain_id": "8453",
"name": "NVDA SuperVault",
"symbol": "superNVDA",
"decimals": 8,
"availability": "available",
"is_supervault_v_2": true,
"has_rewards": true,
"stats_basic": {
"tvl_total": 421256.84,
"apy_snapshot_week": 0,
"reward_rate": 18.29,
"pps": 1.0,
"pps_usd": 233.95
},
"assets": [
{
"address": "0xb20000000000000000000078ee7ce2fe4908108c",
"symbol": "NVDAc",
"decimals": 8,
"price_usd": 233.95
}
]
}
```
## Single vault
```bash
GET https://persephone.superform.xyz/v1/vaults/{id}
```
`id` format: `{chain_id}_{vault_address}`. Same response shape as the list.
## Historical APY and TVL
```bash
GET https://persephone.superform.xyz/v1/vaults/stats/historical/{ids}?start_ts=1727740800&end_ts=1728345600&granularity=day
```
`ids`: comma-separated vault IDs. `granularity`: `hour` or `day`. Returns time-series of `pps`, `tvl_total`, `apy_snapshot_week`, and `reward_rate` for charting.
## User positions
```bash
GET https://persephone.superform.xyz/v1/balances/{address}
```
Returns all vault positions for a wallet: share balance, USD value, vault metadata.
## Price per share
`stats_basic.pps` is the exchange rate between one vault share and the underlying asset. It increases as yield accrues and steps down at each performance fee-skim event. Display user balance as `shares * pps`.
For the full APY formula, how `apy_snapshot_week` is computed, and how to handle the `-1000000` sentinel, see [APY Methodology](/learn/apy-methodology).
---
# Deposit
Source: https://docs.superform.xyz/build/earn/deposit
SuperVaults accept synchronous deposits. The Bundler handles token approval and deposit in one request.
```bash Non-smart-account (EOA / Safe / Fireblocks / custodian)
# Returns ordered transactions to broadcast sequentially.
# Index 0 is ERC20.approve, index 1 is ERC4626.deposit.
POST https://bundler.superform.xyz/hooks/suggest/non-smart-account
Content-Type: application/json
{
"account": "0xUSER_WALLET_ADDRESS",
"action": {
"type": "DEPOSIT",
"slippage": 50,
"sources": [
{
"address": "0xb20000000000000000000078ee7ce2fe4908108c",
"chain_id": 8453,
"amount": "100000000"
}
],
"targets": [
{
"address": "0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
"chain_id": 8453,
"vault_type": "SUPERVAULT",
"amount_proportion": 1
}
],
"route_type": "OUTPUT",
"outputs": []
}
}
```
```bash Smart account (ERC-4337)
# 1. Suggest hooks
POST https://bundler.superform.xyz/hooks/suggest
Content-Type: application/json
{
"account": "0xUSER_SMART_ACCOUNT_ADDRESS",
"companion_mode": false,
"actions": [
{
"type": "DEPOSIT",
"slippage": 50,
"sources": [
{
"address": "0xb20000000000000000000078ee7ce2fe4908108c",
"chain_id": 8453,
"amount": "100000000"
}
],
"targets": [
{
"address": "0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
"chain_id": 8453,
"vault_type": "SUPERVAULT",
"amount_proportion": 1
}
],
"route_type": "OUTPUT",
"outputs": []
}
]
}
# 2. Build UserOps
POST https://bundler.superform.xyz/executor/build
Content-Type: application/json
{
"account": "0xUSER_SMART_ACCOUNT_ADDRESS",
"hooks": [ /* hooks[] from suggest response */ ],
"fee_tokens_to_use": [ /* from suggest response */ ],
"token_balance_changes": [ /* from suggest response */ ],
"signer_type": "EIP4337"
}
# 3. Sign merkle_root then execute
# signature = wallet.signMessage(build_response.merkle_root)
POST https://bundler.superform.xyz/executor/execute
Content-Type: application/json
{
"expiry": 1234567890,
"merkle_root": "0x...",
"user_ops": [ /* from build response */ ],
"valid_after": "0",
"fees_tokens": [ /* from build response */ ],
"signature": "0xUSER_SIGNATURE"
}
# 4. Poll
GET https://bundler.superform.xyz/executor/transactions/{id}
# status: "PENDING" | "CONFIRMED" | "FAILED"
```
```solidity Direct (ERC-7540)
// Two transactions: approve then deposit.
IERC20(0xb20000000000000000000078ee7ce2fe4908108c)
.approve(
0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc,
100000000 // 1 NVDAc (8 decimals)
);
// deposit(uint256 assets, address receiver) returns uint256 shares
uint256 shares = ISuperVault(0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc)
.deposit(100000000, receiverAddress);
```
**non-smart-account response**
```json
{
"executions": [
{
"type": "DEPOSIT",
"to": "0xb20000000000000000000078ee7ce2fe4908108c",
"value": "0",
"call_data": "0x095ea7b3..."
},
{
"type": "DEPOSIT",
"to": "0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
"value": "0",
"call_data": "0x6e553f65..."
}
]
}
```
Index 0 (`0x095ea7b3`) is `ERC20.approve`. Index 1 (`0x6e553f65`) is `ERC4626.deposit`. Both carry `type: "DEPOSIT"`. Use array position to determine order.
**Request parameters**
| Field | Required | Description |
|---|---|---|
| `account` | ✅ | Wallet address holding the source token |
| `action.type` | ✅ | `DEPOSIT` |
| `action.slippage` | ✅ | Basis points (`50` = 0.5%) |
| `sources[].address` | ✅ | Underlying token address |
| `sources[].chain_id` | ✅ | `8453` |
| `sources[].amount` | ✅ | Raw token amount in smallest unit. Use `amount_fiat` for USD value |
| `targets[].address` | ✅ | Vault contract address |
| `targets[].vault_type` | ✅ | `"SUPERVAULT"` |
| `targets[].amount_proportion` | ✅ | `1` to route 100% of source |
> The bundler validates on-chain balance before building. Check balance first with `GET /balances/{address}`.
**Token decimals.** Express `amount` in the token's smallest unit:
| Token | Decimals | 1 token |
|---|---|---|
| USDC | 6 | `"1000000"` |
| WETH | 18 | `"1000000000000000000"` |
| cbBTC | 8 | `"100000000"` |
| NVDAc, TSLAc, AAPLc, GOOGLc, SPCXc, METAc, AMZNc, MSFTc | 8 | `"100000000"` |
| UP (for sUP vault) | 18 | `"1000000000000000000"` |
Use `amount_fiat` instead of `amount` if you only know the USD value.
---
# Redemption
Source: https://docs.superform.xyz/build/earn/redemption
SuperVaults use ERC-7540 async redemptions. Shares go into escrow on request; a keeper fulfills within ~1 hour; the user then claims assets.
**Non-standard ERC-4626 behavior.** `maxWithdraw(account)` and `maxRedeem(account)` return `0` until the keeper fulfills the request. Shares are in escrow; assets have not yet been freed. Poll `pendingRedeemRequest(0, account)` to confirm the request is queued, then poll `maxWithdraw(account)` until positive before sending the claim.
```bash Non-smart-account
# Step A: Request redemption
POST https://bundler.superform.xyz/hooks/suggest/non-smart-account
Content-Type: application/json
{
"account": "0xUSER_WALLET_ADDRESS",
"action": {
"type": "REDEEM",
"slippage": 50,
"sources": [],
"targets": [
{
"address": "0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
"chain_id": 8453,
"vault_type": "SUPERVAULT",
"amount": "100000000"
}
],
"outputs": [
{
"address": "0xb20000000000000000000078ee7ce2fe4908108c",
"chain_id": 8453,
"amount_proportion": 1
}
],
"route_type": "OUTPUT"
}
}
# Broadcast executions[] in order.
# Poll maxWithdraw(account) on the vault contract until > 0.
# Step B: Claim after fulfillment
POST https://bundler.superform.xyz/hooks/suggest/non-smart-account
Content-Type: application/json
{
"account": "0xUSER_WALLET_ADDRESS",
"action": {
"type": "REDEEM_CLAIM",
"slippage": 50,
"sources": [],
"targets": [
{
"address": "0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
"chain_id": 8453,
"vault_type": "SUPERVAULT",
"amount": "100000000"
}
],
"outputs": [
{
"address": "0xb20000000000000000000078ee7ce2fe4908108c",
"chain_id": 8453,
"amount_proportion": 1
}
],
"route_type": "OUTPUT"
}
}
```
```solidity Direct (ERC-7540)
interface ISuperVault {
// Step A: Queue redemption
function requestRedeem(uint256 shares, address controller, address owner)
external returns (uint256 requestId);
// Poll: 0 while pending, > 0 when fulfilled
function maxWithdraw(address controller) external view returns (uint256);
// SuperVault always uses REQUEST_ID = 0
function pendingRedeemRequest(uint256 requestId, address controller)
external view returns (uint256);
// Step B: Claim after maxWithdraw > 0
function withdraw(uint256 assets, address receiver, address controller)
external returns (uint256 shares);
}
// 1. requestRedeem(shares, msg.sender, msg.sender)
// 2. Poll pendingRedeemRequest(0, msg.sender) > 0 to confirm queued
// Poll maxWithdraw(msg.sender) -- proceed when > 0 (keeper fulfilled)
// 3. withdraw(maxWithdraw(msg.sender), receiver, msg.sender)
```
**State machine**
| State | How to detect | Next action |
|---|---|---|
| Pending | `pendingRedeemRequest(0, account) > 0` and `maxWithdraw(account) == 0` | Wait for keeper (~1 hour) |
| Fulfilled | `maxWithdraw(account) > 0` | Send `REDEEM_CLAIM` or call `withdraw()` |
| Claimed | `maxWithdraw(account) == 0` and `pendingRedeemRequest(0, account) == 0` | Done |
---
# Rewards
Source: https://docs.superform.xyz/build/earn/rewards
SuperVault rewards are distributed via [Merkl](https://merkl.xyz). They accrue automatically while users hold vault shares. No staking required.
## Check if a vault has rewards
`has_rewards: true` on the vault response means an active Merkl campaign exists. `stats_basic.reward_rate` is the current reward APR in percentage points.
## Check claimable rewards for a user
```bash
GET https://persephone.superform.xyz/v1/rewards/external/claimable/{address}
```
| Field | Description |
|---|---|
| `chain_id` | Chain the reward is claimable on |
| `extra_data` | Merkl proof — required in the claim bundler call |
| `tokens[].address` | Reward token contract address |
| `tokens[].amount` | Currently claimable amount (resets to 0 after claim) |
| `tokens[].provider_claim_amount` | Cumulative earned — use this as the `amount` in the bundler claim |
| `tokens[].symbol` | Token symbol (e.g. `USDC`) |
| `tokens[].value_usd` | USD value of claimable amount |
| `total_earned_value_usd` | Lifetime earned across all tokens |
**Example response**
```json
{
"total_earned_value_usd": 14.23,
"rewards": [
{
"chain_id": "8453",
"extra_data": "0x...",
"tokens": [
{
"address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"symbol": "USDC",
"decimals": 6,
"amount": "14230000",
"provider_claim_amount": "14230000",
"value_usd": 14.23
}
]
}
]
}
```
## Claim rewards
The `CLAIM` action works on both `/hooks/suggest/non-smart-account` and the smart account flow.
```bash
POST https://bundler.superform.xyz/hooks/suggest/non-smart-account
Content-Type: application/json
{
"account": "0xUSER_WALLET_ADDRESS",
"action": {
"type": "CLAIM",
"slippage": 0,
"sources": [],
"outputs": [
{
"address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"chain_id": 8453,
"amount": "14230000"
}
],
"targets": [
{
"address": "0xUSER_WALLET_ADDRESS",
"chain_id": 8453,
"tags": ["merkl"],
"extra_data": "0x..."
}
],
"route_type": "OUTPUT"
}
}
```
- `outputs[].amount` = `provider_claim_amount` from the claimable response (cumulative, not delta)
- `targets[].extra_data` = `rewards[].extra_data` (Merkl proof)
- `targets[].tags: ["merkl"]` is required
- Max 7 reward tokens per claim call
---
# Client Library
Source: https://docs.superform.xyz/build/sdk
Copy-paste clients for Persephone and the Bundler. No package to install. Thin wrappers around `fetch` (TypeScript) and `httpx` (Python) you can drop into your project.
---
## TypeScript
```typescript
// superform.ts
// No external dependencies — uses native fetch (Node 18+, all browsers)
const PERSEPHONE = "https://persephone.superform.xyz/v1";
const BUNDLER = "https://bundler.superform.xyz";
// ─── Vault Data ──────────────────────────────────────────────────────────────
/** List all SuperVaults on Base. Filter by availability before displaying deposit UI. */
export async function listVaults(chainId = 8453) {
const res = await fetch(`${PERSEPHONE}/supervaults?chain_id=${chainId}`);
if (!res.ok) throw new Error(`listVaults failed: ${res.status}`);
const data = await res.json();
return data.supervaults as Vault[];
}
/** Get a single vault by its stable ID ({chain_id}_{address}). */
export async function getVault(vaultId: string) {
const res = await fetch(`${PERSEPHONE}/vaults/${vaultId}`);
if (!res.ok) throw new Error(`getVault failed: ${res.status}`);
const data = await res.json();
return (data.vault ?? data) as Vault;
}
/** Get historical APY and TVL for one or more vaults. */
export async function getVaultHistory(
vaultIds: string[],
startTs: number,
endTs: number,
granularity: "day" | "hour" = "day"
) {
const ids = vaultIds.join(",");
const res = await fetch(
`${PERSEPHONE}/vaults/stats/historical/${ids}?start_ts=${startTs}&end_ts=${endTs}&granularity=${granularity}`
);
if (!res.ok) throw new Error(`getVaultHistory failed: ${res.status}`);
return res.json();
}
/** Get all vault positions for a wallet address. */
export async function getPositions(address: string) {
const res = await fetch(`${PERSEPHONE}/balances/${address}`);
if (!res.ok) throw new Error(`getPositions failed: ${res.status}`);
return res.json();
}
/** Get claimable Merkl rewards for a wallet address. */
export async function getClaimableRewards(address: string) {
const res = await fetch(`${PERSEPHONE}/rewards/external/claimable/${address}`);
if (!res.ok) throw new Error(`getClaimableRewards failed: ${res.status}`);
return res.json() as Promise;
}
// ─── Transactions ─────────────────────────────────────────────────────────────
/**
* Build a deposit transaction for a non-smart-account wallet (EOA / Safe / Fireblocks / custodian).
* Returns an ordered list of executions to broadcast sequentially.
* Index 0 is ERC20.approve, index 1 is ERC4626.deposit.
*/
export async function buildDeposit(
account: string,
sourceTokenAddress: string,
sourceChainId: number,
amount: string, // raw token amount in smallest unit
vaultAddress: string,
vaultChainId: number
): Promise {
const res = await fetch(`${BUNDLER}/hooks/suggest/non-smart-account`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
account,
action: {
type: "DEPOSIT",
slippage: 50,
sources: [{ address: sourceTokenAddress, chain_id: sourceChainId, amount }],
targets: [{ address: vaultAddress, chain_id: vaultChainId, vault_type: "SUPERVAULT", amount_proportion: 1 }],
route_type: "OUTPUT",
outputs: [],
},
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(`buildDeposit failed ${res.status}: ${err.message ?? res.statusText}`);
}
const data = await res.json();
return data.executions as Execution[];
}
/**
* Build a redemption request for a non-smart-account wallet.
* After broadcasting, poll maxWithdraw(account) on the vault contract.
* When > 0, call buildRedeemClaim.
*/
export async function buildRedeem(
account: string,
vaultAddress: string,
vaultChainId: number,
shares: string, // share amount to redeem in smallest unit
outputTokenAddress: string
): Promise {
const res = await fetch(`${BUNDLER}/hooks/suggest/non-smart-account`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
account,
action: {
type: "REDEEM",
slippage: 50,
sources: [],
targets: [{ address: vaultAddress, chain_id: vaultChainId, vault_type: "SUPERVAULT", amount: shares }],
outputs: [{ address: outputTokenAddress, chain_id: vaultChainId, amount_proportion: 1 }],
route_type: "OUTPUT",
},
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(`buildRedeem failed ${res.status}: ${err.message ?? res.statusText}`);
}
const data = await res.json();
return data.executions as Execution[];
}
/** Build a redemption claim after the keeper has fulfilled the request. */
export async function buildRedeemClaim(
account: string,
vaultAddress: string,
vaultChainId: number,
shares: string,
outputTokenAddress: string
): Promise {
const res = await fetch(`${BUNDLER}/hooks/suggest/non-smart-account`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
account,
action: {
type: "REDEEM_CLAIM",
slippage: 50,
sources: [],
targets: [{ address: vaultAddress, chain_id: vaultChainId, vault_type: "SUPERVAULT", amount: shares }],
outputs: [{ address: outputTokenAddress, chain_id: vaultChainId, amount_proportion: 1 }],
route_type: "OUTPUT",
},
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(`buildRedeemClaim failed ${res.status}: ${err.message ?? res.statusText}`);
}
const data = await res.json();
return data.executions as Execution[];
}
// ─── Helpers ──────────────────────────────────────────────────────────────────
/** Returns total APY in percentage points. Filters the -1000000 no-data sentinel. */
export function totalApy(vault: Vault): number {
const strategy = vault.stats_basic?.apy_snapshot_week ?? 0;
const rewards = vault.stats_basic?.reward_rate ?? 0;
const safeStrategy = strategy < -999999 ? 0 : strategy;
const safeRewards = rewards < -999999 ? 0 : rewards;
return safeStrategy + safeRewards;
}
/** Format APY for display. Returns "—" for the no-data sentinel. */
export function formatApy(value: number): string {
if (value < -999999) return "—";
return value.toFixed(2) + "%";
}
/** Format a user's vault balance in underlying asset units. */
export function formatBalance(shares: bigint, pps: number, decimals: number): string {
return ((Number(shares) / 10 ** decimals) * pps).toFixed(6);
}
// ─── Types ────────────────────────────────────────────────────────────────────
export interface Vault {
id: string;
address: string;
chain_id: string;
name: string;
friendly_name: string;
symbol: string;
decimals: number;
availability: "available" | "withdraw_only" | "deposit_only" | "paused" | "unavailable";
is_supervault_v_2: boolean;
has_rewards: boolean;
stats_basic: {
tvl_total: number;
apy_snapshot_now: number;
apy_snapshot_day: number;
apy_snapshot_week: number;
apy_snapshot_month: number;
reward_rate: number;
pps: number;
pps_usd: number;
};
assets: Array<{
address: string;
symbol: string;
decimals: number;
price_usd: number;
}>;
}
export interface Execution {
type: string;
to: string;
value: string;
call_data: string;
chain_id?: number;
}
export interface RewardsResponse {
total_earned_value_usd: number;
rewards: Array<{
chain_id: string;
extra_data: string;
tokens: Array<{
address: string;
symbol: string;
decimals: number;
amount: string;
provider_claim_amount: string;
value_usd: number;
}>;
}>;
}
```
---
## Python
```python
# superform.py
# Requires: httpx (pip install httpx)
# Or swap httpx for requests — the interface is identical.
import httpx
from typing import Optional
PERSEPHONE = "https://persephone.superform.xyz/v1"
BUNDLER = "https://bundler.superform.xyz"
class SuperformClient:
def __init__(self, timeout: int = 15):
self.http = httpx.Client(timeout=timeout)
# ── Vault Data ───────────────────────────────────────────────────────────
def list_vaults(self, chain_id: int = 8453) -> list[dict]:
"""List all SuperVaults. Filter availability before displaying deposit UI."""
r = self.http.get(f"{PERSEPHONE}/supervaults", params={"chain_id": chain_id})
r.raise_for_status()
return r.json()["supervaults"]
def get_vault(self, vault_id: str) -> dict:
"""Get a single vault by stable ID ({chain_id}_{address})."""
r = self.http.get(f"{PERSEPHONE}/vaults/{vault_id}")
r.raise_for_status()
data = r.json()
return data.get("vault", data)
def get_vault_history(
self,
vault_ids: list[str],
start_ts: int,
end_ts: int,
granularity: str = "day",
) -> dict:
"""Historical APY and TVL. granularity: 'day' or 'hour'."""
ids = ",".join(vault_ids)
r = self.http.get(
f"{PERSEPHONE}/vaults/stats/historical/{ids}",
params={"start_ts": start_ts, "end_ts": end_ts, "granularity": granularity},
)
r.raise_for_status()
return r.json()
def get_positions(self, address: str) -> dict:
"""All vault positions for a wallet address."""
r = self.http.get(f"{PERSEPHONE}/balances/{address}")
r.raise_for_status()
return r.json()
def get_claimable_rewards(self, address: str) -> dict:
"""Claimable Merkl rewards for a wallet address."""
r = self.http.get(f"{PERSEPHONE}/rewards/external/claimable/{address}")
r.raise_for_status()
return r.json()
# ── Transactions ─────────────────────────────────────────────────────────
def build_deposit(
self,
account: str,
source_token: str,
source_chain_id: int,
amount: str, # raw token amount in smallest unit
vault_address: str,
vault_chain_id: int,
slippage: int = 50,
) -> list[dict]:
"""
Build a deposit for a non-smart-account wallet (EOA / Safe / Fireblocks / custodian).
Returns ordered executions[]. Index 0 = approve, index 1 = deposit.
Broadcast each in sequence; wait for confirmation before the next.
"""
r = self.http.post(
f"{BUNDLER}/hooks/suggest/non-smart-account",
headers={},
json={
"account": account,
"action": {
"type": "DEPOSIT",
"slippage": slippage,
"sources": [{"address": source_token, "chain_id": source_chain_id, "amount": amount}],
"targets": [{"address": vault_address, "chain_id": vault_chain_id, "vault_type": "SUPERVAULT", "amount_proportion": 1}],
"route_type": "OUTPUT",
"outputs": [],
},
},
)
r.raise_for_status()
return r.json()["executions"]
def build_redeem(
self,
account: str,
vault_address: str,
vault_chain_id: int,
shares: str, # share amount in smallest unit
output_token: str,
slippage: int = 50,
) -> list[dict]:
"""
Build a redemption request.
After broadcasting, poll vault.maxWithdraw(account) onchain.
When > 0, call build_redeem_claim.
"""
r = self.http.post(
f"{BUNDLER}/hooks/suggest/non-smart-account",
headers={},
json={
"account": account,
"action": {
"type": "REDEEM",
"slippage": slippage,
"sources": [],
"targets": [{"address": vault_address, "chain_id": vault_chain_id, "vault_type": "SUPERVAULT", "amount": shares}],
"outputs": [{"address": output_token, "chain_id": vault_chain_id, "amount_proportion": 1}],
"route_type": "OUTPUT",
},
},
)
r.raise_for_status()
return r.json()["executions"]
def build_redeem_claim(
self,
account: str,
vault_address: str,
vault_chain_id: int,
shares: str,
output_token: str,
slippage: int = 50,
) -> list[dict]:
"""Claim a fulfilled redemption (after maxWithdraw(account) > 0)."""
r = self.http.post(
f"{BUNDLER}/hooks/suggest/non-smart-account",
headers={},
json={
"account": account,
"action": {
"type": "REDEEM_CLAIM",
"slippage": slippage,
"sources": [],
"targets": [{"address": vault_address, "chain_id": vault_chain_id, "vault_type": "SUPERVAULT", "amount": shares}],
"outputs": [{"address": output_token, "chain_id": vault_chain_id, "amount_proportion": 1}],
"route_type": "OUTPUT",
},
},
)
r.raise_for_status()
return r.json()["executions"]
# ── Helpers ──────────────────────────────────────────────────────────────
@staticmethod
def total_apy(vault: dict) -> float:
"""
Total APY in percentage points. Filters the -1000000 no-data sentinel.
"""
NO_DATA = -1_000_000
s = vault.get("stats_basic", {})
strategy = s.get("apy_snapshot_week", 0) or 0
rewards = s.get("reward_rate", 0) or 0
return (0 if strategy < -999999 else strategy) + (0 if rewards < -999999 else rewards)
@staticmethod
def format_apy(value: float) -> str:
"""Format APY for display. Returns '—' for the no-data sentinel."""
if value < -999999:
return "—"
return f"{value:.2f}%"
def close(self):
self.http.close()
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
```
---
## Usage Examples
### TypeScript — display all available SuperStocks vaults
```typescript
import { listVaults, totalApy, formatApy } from "./superform";
const vaults = await listVaults(8453);
const stocks = vaults.filter(
(v) => v.is_supervault_v_2 && v.availability === "available"
);
for (const vault of stocks) {
console.log(`${vault.name}: ${formatApy(totalApy(vault))} APY, $${vault.stats_basic.tvl_total.toFixed(0)} TVL`);
}
// NVDA SuperVault: 18.29% APY, $421256 TVL
// TSLA SuperVault: 37.13% APY, $187440 TVL
```
### TypeScript — deposit 1 NVDAc into the NVDA SuperVault
```typescript
import { buildDeposit } from "./superform";
const executions = await buildDeposit(
"0xYOUR_WALLET",
"0xb20000000000000000000078ee7ce2fe4908108c", // NVDAc
8453,
"100000000", // 1 NVDAc (8 decimals)
"0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc", // NVDA SuperVault
8453
);
// Broadcast each transaction in order — wait for confirmation before the next
for (const tx of executions) {
const hash = await wallet.sendTransaction({ to: tx.to, data: tx.call_data, value: BigInt(tx.value) });
await provider.waitForTransaction(hash);
}
```
### Python — deposit 1 NVDAc
```python
from superform import SuperformClient
with SuperformClient() as sf:
executions = sf.build_deposit(
account="0xYOUR_WALLET",
source_token="0xb20000000000000000000078ee7ce2fe4908108c", # NVDAc
source_chain_id=8453,
amount="100000000", # 1 NVDAc (8 decimals)
vault_address="0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc",
vault_chain_id=8453,
)
for tx in executions:
# Sign and broadcast tx["call_data"] to tx["to"] with value=tx["value"]
print(f"Send tx: {tx['type']} → {tx['to']}")
```
### Python — check claimable rewards
```python
from superform import SuperformClient
with SuperformClient() as sf:
rewards = sf.get_claimable_rewards("0xYOUR_WALLET")
print(f"Total claimable: ${rewards['total_earned_value_usd']:.2f}")
for chain in rewards["rewards"]:
for token in chain["tokens"]:
print(f" {token['symbol']}: {int(token['amount']) / 10**token['decimals']:.6f} (${token['value_usd']:.2f})")
```
---
## Error Handling
The bundler returns structured errors. Always check the HTTP status and parse the `message` field to surface meaningful errors to users.
```typescript
// TypeScript — typed bundler error
interface BundlerError {
code: number;
message: string;
}
async function depositWithErrorHandling(
account: string,
tokenAddress: string,
chainId: number,
amount: string,
vaultAddress: string
): Promise {
const res = await fetch(`${BUNDLER}/hooks/suggest/non-smart-account`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
account,
action: {
type: "DEPOSIT",
slippage: 50,
sources: [{ address: tokenAddress, chain_id: chainId, amount }],
targets: [{ address: vaultAddress, chain_id: chainId, vault_type: "SUPERVAULT", amount_proportion: 1 }],
route_type: "OUTPUT",
outputs: [],
},
}),
});
if (!res.ok) {
const err: BundlerError = await res.json().catch(() => ({ code: res.status, message: res.statusText }));
switch (res.status) {
case 400:
if (err.message.includes("insufficient balance")) {
throw new Error(`Wallet does not hold enough of the source token. Check balance before calling the bundler.`);
}
if (err.message.includes("vault share balance not found")) {
throw new Error(`Wallet holds no shares in this vault. Confirm the user has an active position before redeeming.`);
}
if (err.message.includes("target amount") && err.message.includes("not set")) {
throw new Error(`Missing amount_proportion or amount on target. Add "amount_proportion": 1.`);
}
if (err.message.includes("HOOK_NOT_FOUND")) {
throw new Error("Stale hook ID — always call /hooks/suggest first, never hardcode hook IDs.");
}
throw new Error(`Bad request: ${err.message}`);
case 500:
throw new Error(`Bundler internal error: ${err.message}`);
default:
throw new Error(`Unexpected error ${res.status}: ${err.message}`);
}
}
const data = await res.json();
return data.executions as Execution[];
}
```
```python
# Python — error handling with httpx
import httpx
class BundlerError(Exception):
def __init__(self, status: int, message: str):
self.status = status
self.message = message
super().__init__(f"Bundler error {status}: {message}")
def _handle_bundler_error(r: httpx.Response) -> None:
"""Raise a descriptive BundlerError from a non-2xx bundler response."""
try:
body = r.json()
message = body.get("message", r.text)
except Exception:
message = r.text
if r.status_code == 400:
if "insufficient balance" in message:
raise BundlerError(400, "Wallet does not hold enough of the source token. Check balance first.")
if "vault share balance not found" in message:
raise BundlerError(400, "Wallet holds no shares in this vault.")
if "target amount" in message and "not set" in message:
raise BundlerError(400, "Missing amount_proportion on target. Add amount_proportion=1.")
raise BundlerError(400, message)
if "HOOK_NOT_FOUND" in message:
raise BundlerError(400, "Stale hook ID — always call /hooks/suggest first.")
raise BundlerError(r.status_code, message)
```
**Error message substrings** (match against the `message` field):
| HTTP | Message contains | Cause |
|---|---|---|
| `400` | `insufficient balance` | Source wallet balance too low |
| `400` | `vault share balance not found` | No shares held (for `REDEEM`) |
| `400` | `target amount...is not set` | Missing `amount_proportion` on target |
| `400` | `failed to validate source amount` | Missing `sources[].amount` |
| `400` | `HOOK_NOT_FOUND` | Never call `/executor/build` directly with hardcoded hook IDs |
Bundler errors are deterministic. An identical retry returns the same error. Fix the request before retrying.
## Error Reference
Message-level lookup for the bundler error table. Match against the `message` field in the JSON error response.
| HTTP | Message contains | Cause | Fix |
|---|---|---|---|
| `400` | `insufficient balance` | `account` doesn't hold enough source token | Check balance first: `GET /balances/{address}` |
| `400` | `vault share balance not found` | `account` holds no shares (for `REDEEM`) | Confirm shares held before requesting redemption |
| `400` | `target amount...is not set` | Missing `amount`, `amount_fiat`, or `amount_proportion` on target | Add `"amount_proportion": 1` |
| `400` | `failed to validate source amount` | `sources[].amount` missing | Supply `"amount": ""` or `"amount_fiat": ` |
| `400` | `HOOK_NOT_FOUND` | Stale hook ID passed to `/executor/build` | Always call `/hooks/suggest` first; never hardcode hook IDs |
---
## Token Decimals Reference
| Token | Decimals | 1 token as raw amount |
|---|---|---|
| USDC | 6 | `"1000000"` |
| WETH | 18 | `"1000000000000000000"` |
| cbBTC | 8 | `"100000000"` |
| NVDAc, TSLAc, AAPLc, GOOGLc, SPCXc, METAc, AMZNc, MSFTc | 8 | `"100000000"` |
| UP (for sUP vault) | 18 | `"1000000000000000000"` |
| superNVDA, superTSLA, ... (share tokens) | 8 | `"100000000"` |
---
# APY Methodology
Source: https://docs.superform.xyz/learn/apy-methodology
## Price Per Share (PPS)
All APY computations start from **price per share (PPS)**: the exchange rate between one vault share and the underlying asset.
```
PPS = totalAssets / totalSupply
```
Where `totalAssets` is the sum of:
- Idle assets held in the strategy contract
- The TVL value of each whitelisted yield source, computed via its paired oracle
PPS is stored onchain by the `ECDSAPPSOracle` after validators attest with supermajority consensus. PPS increases as yield accrues and decreases at each performance fee-skim event (the fee is deducted from assets before the new PPS is written, stepping PPS down to the high-water mark). Net direction over time is upward when the vault is generating yield.
Persephone tracks PPS hourly from on-chain events and backfills historical points.
---
## apy_snapshot_* Fields
The `stats_basic` object on every vault exposes four trailing APY windows:
| Field | Window | Description |
|---|---|---|
| `apy_snapshot_now` | 1 hour | PPS change over the last hour, annualized |
| `apy_snapshot_day` | 24 hours | PPS change over the last 24 hours, annualized |
| `apy_snapshot_week` | 7 days | PPS change over the last 7 days, annualized |
| `apy_snapshot_month` | 30 days | PPS change over the last 30 days, annualized |
**Formula:**
```
apy = ((currentPPS / previousPPS) ^ (31,536,000 / windowSeconds)) - 1
```
Expressed as a **percentage** (multiply by 100, rounded to 4 decimal places):
```
apy_snapshot_week = ((pps_now / pps_7d_ago) ^ (31536000 / 604800) - 1) × 100
```
Where:
- `pps_now` = current PPS from the latest hourly record
- `pps_7d_ago` = PPS from 7 days (604,800 seconds) prior
- `31,536,000` = seconds in a year
**Units:** percentage points. `18.29` = 18.29% annualized. Do **not** multiply by 100 again.
**This is pure price appreciation.** It measures how much the vault's share value grew relative to the underlying asset. It does not include Merkl reward tokens, which are tracked separately in `reward_rate`.
---
## reward_rate
`stats_basic.reward_rate` is the current Merkl reward APR in **percentage points**.
It is computed by Persephone's Merkl sync service from live Merkl campaign data:
```
reward_rate = (daily_rewards_usd / vault_tvl_usd) × 365 × 100
```
Where:
- `daily_rewards_usd` = USD value of reward tokens distributed per day to this vault's depositors
- `vault_tvl_usd` = current vault TVL in USD
Separate from `apy_snapshot_*` because reward tokens (USDC, UP) are not reflected in PPS. They accrue in Merkl and are claimed separately.
---
## Total APY
Display total APY as:
```
total_apy = apy_snapshot_week + reward_rate
```
For SuperStocks vaults (`superNVDA`, `superTSLA`, etc.), `reward_rate` is the primary yield today. `apy_snapshot_week` reflects realized strategy yield; it will be near zero while vaults are in early operation and grows as strategies are deployed.
For stablecoin vaults (`superUSDC`, `superStocksUSDC`), both fields are meaningful and should be summed.
---
## The -1000000 Sentinel
Any APY field may return `-1000000` (the Persephone no-data sentinel constant).
```go
const DatamatNoDataSentinel = -1_000_000
```
This value appears when:
- The vault has insufficient PPS history for the requested window (e.g. launched less than 7 days ago for `apy_snapshot_week`)
- The computed APY is mathematically invalid (zero PPS, NaN, or infinity)
- The vault had no PPS update during the measurement window
**Always filter before displaying:**
```typescript
function displayApy(rawValue: number): string {
if (rawValue === -1000000 || rawValue < -999999) return "—";
return rawValue.toFixed(2) + "%";
}
```
**Never pass the sentinel to arithmetic.** Summing `-1000000 + 18.29` gives a nonsense result.
---
## TVL
`stats_basic.tvl_total` is in **USD**, computed as:
```
tvl_total = (totalAssets / 10^decimals) × asset_price_usd
```
Where `asset_price_usd` is fetched from the Persephone token pricing service.
TVL is updated on every hourly PPS snapshot.
---
## Historical Data
```bash
GET https://persephone.superform.xyz/v1/vaults/stats/historical/{ids}?start_ts={unix}&end_ts={unix}&granularity=day
```
Returns time-series of `pps`, `tvl_total`, `apy_snapshot_week`, and `reward_rate` per day or hour. Use for charts and performance history.
---
## Display Guidance
| Field | Units | Display |
|---|---|---|
| `apy_snapshot_week` | % points | `apy_snapshot_week.toFixed(2) + "%"` — if `-1000000`, show `"—"` |
| `reward_rate` | % points | `reward_rate.toFixed(2) + "%"` — if `0` and `has_rewards: false`, show `"—"` |
| `stats_basic.pps` | underlying asset units | `(shares × pps).toFixed(6)` for user balance |
| `stats_basic.pps_usd` | USD | `"$" + (shares × pps_usd).toFixed(2)` for USD balance |
| `tvl_total` | USD | `"$" + (tvl_total / 1e6).toFixed(2) + "M"` for display |
---
# Write a Hook
Source: https://docs.superform.xyz/build/hooks
Hooks are lightweight, permissionless contracts which perform a specific action (*bridge, swap, lend*, etc.). They are designed to be composable and can be chained together to create complex transaction flows which can achieve any multi-chain strategy. Hook chains can be fulfilled by any ERC-7579 smart account with the SuperValidator and SuperExecutor modules installed.
## Key Concepts
Hooks implement transient storage during the execution of a transaction to temporarily hold state changes. This avoids the gas costs of permanent storage writes between hooks. If any hook in a chain fails, the entire transaction is reverted, ensuring atomicity.
### Hook Lifecycle
Each hook runs a three-step internal sequence inside its `build()` function:
`preExecute` → `execution(s)` → `postExecute`
The full protocol flow that wraps that sequence has four steps:
1. **Build** - Frontend selects the addresses and calldata of the hook(s) in a chain.
2. **Bundle** - SuperBundler splits hooks per chain and wraps each into an ERC-4337 UserOp, storing a Merkle tree.
3. **Validate** - Smart Account delegates to SuperValidator onchain to perform Merkle proof verification.
4. **Execute** - SuperExecutor iterates through hooks, performs each action and updates SuperLedger.
### Hook Classifications
Hooks are classified based on `HookType` and `HookSubType`.
`HookType` must be defined as `NONACCOUNTING`, `INFLOW`, or `OUTFLOW` depending on the hook's expected interaction with the accounting system.
`HookSubTypes` designate which action category the hook implements.
- **NONACCOUNTING** - Utility hooks that do not update the accounting system. These hooks include `HookSubTypes` like `SWAP`, `STAKE`, `BRIDGE`.
- **INFLOW** - Hooks that process additions to positions and typically encode some sort of `deposit` action. They include a `HookSubType` corresponding to the underlying vault being deposited into like `ERC4626`, `ERC7540`, etc.
- **OUTFLOW** - Hooks that process reductions to positions and typically encode some sort of `redeem` action. They include similar `HookSubType` to `INFLOW` hooks.
## Quick Start
**Requirements**
- `Foundry ≥ v1.3.0`
- `Solidity 0.8.30`
- `pnpm`
- `yarn`
**Repo Setup**
Clone the public repo and its submodules:
```bash
git clone --recursive https://github.com/superform-xyz/v2-core.git
cd v2-core
```
Install dependencies:
```bash
forge install
cd lib/modulekit
pnpm install
cd ../..
cd lib/safe7579
pnpm install
cd ../..
cd lib/nexus
yarn
cd ../..
```
Copy the environment file:
```bash
cp .env.example .env
```
**Build & Testing**
Compile the contracts:
```bash
forge build
```
Run tests:
```bash
# Supply your node rpc directly in the makefile
# Run all tests
make ftest
# Run a specific test
# Replace the test in the `make test-vvv` command with the test to be run
make test-vvv
```
## Anatomy of a Hook
1. Every hook must inherit [BaseHook](https://github.com/superform-xyz/v2-core/blob/dev/src/hooks/BaseHook.sol) to obtain core hook functionality, along with any specific interfaces your hook may require from [ISuperHook](https://github.com/superform-xyz/v2-core/blob/dev/src/interfaces/ISuperHook.sol).
2. Each hook contract begins with natspec detailing the layout of the hook data, this ensures that SuperBundler is able to encode the data required for hook execution.
1. The natspec of the data structure must be placed after the line saying `@dev data has the following structure` and the natspec must follow the format of `@notice DataType Name Offset`.
2. An example of a hook with a simple encoding structure can be found [here](https://github.com/superform-xyz/v2-core/blob/dev/src/hooks/loan/morpho/MorphoSupplyAndBorrowHook.sol#L19). [This](https://github.com/superform-xyz/v2-core/blob/dev/src/hooks/bridges/debridge/DeBridgeSendOrderAndExecuteOnDstHook.sol#L16) is a more complex example of encoding natspec.
3. The constructor sets the `HookType` and `HookSubtype` as well as any immutable state variables such as the target contract address to call during execution. For a breakdown of different `HookType` options see [Hook Classifications](#hook-classifications), and the `HookSubTypes` are found in [this library](https://github.com/superform-xyz/v2-core/blob/dev/src/libraries/HookSubTypes.sol). If your hook will perform an action type that is not yet in the library, add it there as `bytes32 public constant CATEGORY_NAME`.
4. It is necessary to decode and validate the encoded hook data used by the hook. It is recommended to create some internal decoder and validation helpers. The decoding must align with the data locations denoted in the natspec as that is the order in which data will be encoded. There are two libraries you can use during the decoding process:
1. `BytesLib` - This necessary library can be found in the `src/vendor` directory, you need only import it to gain access to its functionality.
2. `HookDataDecoder` - This library lives in the `src/libraries` directory. It must be imported and the statement `using HookDataDecoder for bytes` must be placed at the top of the hook contract.
5. The `_buildHookExecutions()` function takes decoded hook data and returns an array of Executions.
1. There are 3 parameters for this function:
1. `prevHook` - The address of the prior hook in a chain, `address(0)` if this hook is unchained or the first in a chain. Note: If you intend for this hook to be after another hook in a chain `bool usePrevHookAmount` must be added as a parameter to be encoded in the hook data structure natspec, and `ISuperHookResult` is required.
2. `account` - The smart account address if needed for the Execution payload.
3. `data` - The bytes hook data (encoded in the structure defined by the natspec)
2. The payload of an Execution is as follows:
1. `Target` - The address to call (contract or another module)
2. `Value` - Native ETH amount to forward as `msg.value`
3. `Calldata` - The encoded function selector to be called and any arguments
## Hook Contract Outline
```solidity
/// @title MyHook
/// @author SomeDev
/// @dev data has the following structure
/// @notice DataType Name Offset
contract MyHook is BaseHook {
// Constructor: set hook type + subtype
constructor()
BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) {}
// 1️⃣ Build the array of Execution structs
function _buildHookExecutions(
address prevHook,
address account,
bytes calldata data
) internal view override returns (Execution[] memory execs) { … }
// 2️⃣ Validate & prepare (state‑changing)
function _preExecute(
address prevHook,
address account,
bytes calldata data
) internal override { … }
// 3️⃣ Finalise, set outAmount / usedShares
function _postExecute(
address prevHook,
address account,
bytes calldata data
) internal override { … }
}
```
Key fields inherited from `BaseHook`:
| Variable | Purpose |
|----------|---------|
| `hookType` | Enum for accounting engine |
| `subType` | 32-byte tag for analytics / routing |
| `usedShares`, `spToken`, `asset` | Transient storage outputs consumed by later hooks |
| `setOutAmount(uint256,address)` | Records how many units this hook produced |
See the full base contract for inline docs [here](https://github.com/superform-xyz/v2-core/raw/dev/src/hooks/BaseHook.sol).
## Writing your First Hook: Approve-and-Deposit ERC-4626
We'll re-create the example at
`src/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol` ([GitHub](https://github.com/superform-xyz/v2-core/raw/dev/src/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol)).
Approve the vault, deposit `amount` into it, and emit the number of shares received so that downstream hooks can compose on it.
### Hook Data Layout
| Offset | Bytes | Field |
|--------|-------|-------|
| 0 | 32 | `yieldSourceOracleId` *(optional)* |
| 32 | 20 | `yieldSource` (`IERC4626` vault) |
| 52 | 20 | `token` (underlying) |
| 72 | 32 | `amount` (uint256) |
| 104 | 1 | `usePrevHookAmount` (bool) |
### Implementation Highlights
```solidity
/// 1. Create the hook natspec
/// @title ApproveAndDeposit4626VaultHook Tutorial
/// @dev data has the following structure
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
/// @notice address yieldSource = BytesLib.toAddress(data, 32);
/// @notice address token = BytesLib.toAddress(data, 52);
/// @notice uint256 amount = BytesLib.toUint256(data, 72);
/// @notice bool usePrevHookAmount = _decodeBool(data, 104);
contract ApproveAndDeposit4626VaultHook is
BaseHook, // 2. Implement inheritance
ISuperHookInflowOutflow,
ISuperHookContextAware
{
using HookDataDecoder for bytes; // 3. Import and setup decoder libraries
uint256 private constant AMOUNT_POS = 72;
uint256 private constant USE_PREV_POS = 104;
// 4. Set categories in constructor
constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) { }
/* ---------- build() internals ---------- */
// 5. Create the build executions
function _buildHookExecutions(
address prev,
address account,
bytes calldata data
) internal view override returns (Execution[] memory ex) {
// 5.1 Decode data
address yieldSource = data.extractYieldSource();
address token = BytesLib.toAddress(data, 52);
uint256 amount = _decodeAmount(data);
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
// 5.2 Hook chaining functionality
if (usePrevHookAmount) {
amount = ISuperHookResult(prevHook).getOutAmount(account);
} // Note: re-use prevHook's output when usePrevHookAmount = true
// 5.3 Data validation
if (amount == 0) revert AMOUNT_NOT_VALID();
if (yieldSource == address(0) || token == address(0)) revert ADDRESS_NOT_VALID();
// 5.4.1 Define Executions
// 4 calls = 4 executions: reset approval → approve → deposit → reset approval
executions = new Execution[](4);
// 5.4.2 Populate execution elements
executions[0] =
Execution({
target: token,
value: 0,
callData: abi.encodeCall(IERC20.approve, (yieldSource, 0))
});
executions[1] =
Execution({
target: token,
value: 0,
callData: abi.encodeCall(IERC20.approve, (yieldSource, amount))
});
executions[2] =
Execution({
target: yieldSource,
value: 0,
callData: abi.encodeCall(IERC4626.deposit, (amount, account))
});
executions[3] =
Execution({
target: token,
value: 0,
callData: abi.encodeCall(IERC20.approve, (yieldSource, 0))
}); // Security note: zero allowances before/after deposit to avoid "variable-approval" exploit
}
/* ---------- pre / post ---------- */
// 6. Validate & prepare any state-changing amounts required
function _preExecute(
address, address account, bytes calldata data
) internal override {
_setOutAmount(_vaultBal(account, data), account); // snapshot
spToken = data.extractYieldSource();
}
// 7. Finalise, set outAmount/usedShares
function _postExecute(
address, address account, bytes calldata data
) internal override {
uint256 afterBal = _vaultBal(account, data);
_setOutAmount(afterBal - getOutAmount(account), account);
}
/* ---------- helpers ---------- */
function _decodeAmount(bytes memory data) private pure returns (uint256) {
return BytesLib.toUint256(data, AMOUNT_POS);
}
function _vaultBal(address acct, bytes memory d)
private view returns (uint256)
{
return IERC4626(d.extractYieldSource()).balanceOf(acct);
}
}
```
To test the newly created hook follow the steps outlined in the [Quick Start](#quick-start) section above.
---
# Become a Validator
Source: https://docs.superform.xyz/build/become-a-validator/index
The validator program is currently permissioned. Ready to join? [Apply to become a validator](https://docs.google.com/forms/d/e/1FAIpQLSc63kVcrBLYYs2dtl16ZMABjNy5lRNBrHHcfOS6RUiARYU-fg/viewform?usp=send_form&pli=1&authuser=0).
Before you start, confirm you have an onboarding contact at Superform and access to the validator artifacts you plan to run (published image or source repository), bootstrap peers, and the chain-specific contract addresses for your assigned deployment.
Ready to join? [Apply to become a validator](https://docs.google.com/forms/d/e/1FAIpQLSc63kVcrBLYYs2dtl16ZMABjNy5lRNBrHHcfOS6RUiARYU-fg/viewform?usp=send_form&pli=1&authuser=0).
## What this program is
The SuperVaults Validator Network is the oracle layer that keeps vault price-per-share (PPS) data fresh and tamper-resistant. Validators run an OCR2-based node that:
- observes PPS on each configured chain
- signs consensus reports with a dedicated onchain key
- helps submit a single quorum-backed update onchain
- earns validator rewards for reliable participation
## Who should run a validator
This program is designed for operators who can run production infrastructure consistently, manage keys safely, and respond to incidents quickly. It is not a casual self-serve node today.
## Before you touch a server
Have these inputs lined up first:
- **Program access** — confirmation from Superform that your organization is approved to join
- **Runtime artifacts** — access to the published container image or the source repository and templates
- **Infrastructure** — a Linux or macOS host, PostgreSQL, stable outbound connectivity, and reliable RPC providers
- **Network metadata** — bootstrap peers, the current config version, and the contract addresses Superform wants you to monitor
- **Operator contact path** — a place to submit your public key bundle and coordinate registration
If any of those are missing, stop there. Most validator setup failures come from missing onboarding inputs, not from the software install itself.
## Read this section in order
The shortest safe path from approval to a running node.
Canonical install and runtime guide for Docker or source deployments.
Config reference for config.toml, chains.yaml, and OCR2 timing presets.
Health endpoints, metrics, and alert rules for production operators.
Registration, upgrades, key rotation, monitoring checks, and troubleshooting.
## Supporting context
OCR2 rounds, quorum, failure modes, and validator config mechanics.
Rewards, staking, slashing, insurance, and rollout phases.
## Operator model at a glance
| Topic | Current model |
|-------|---------------|
| Access | Permissioned onboarding through Superform |
| Validator registration | Superform submits `setValidatorConfig()` |
| Node reconfiguration | Automatic from `ValidatorConfigSet` events |
| Key custody | Operator-managed; onchain key should use KMS in production |
| Rewards and slashing | Phase 2 design, not live in Phase 1 |
## Safe order of operations
1. Confirm access and receive the required network metadata.
2. Stand up the node and validate local health.
3. Export and submit your public key bundle.
4. Wait for Superform to register your validator onchain.
5. Verify your node loads the new config and starts participating.
6. Put monitoring and incident response in place before treating the node as production.
---
# Quickstart
Source: https://docs.superform.xyz/build/become-a-validator/quickstart
Use this page when you want the fastest end-to-end path. It intentionally skips deep explanation. If you need full detail, jump to [Infrastructure Sizing](/build/become-a-validator/infrastructure), [Node Setup](/build/become-a-validator/node-setup), [Configuration Reference](/build/become-a-validator/configuration-reference), or [Operations](/build/become-a-validator/operations).
Do not start here until Superform has confirmed you are approved to join and you have the required network inputs: bootstrap peers, chain-specific contract addresses, current config version, and a place to submit your public key bundle.
Ready to join? [Apply to become a validator](https://docs.google.com/forms/d/e/1FAIpQLSc63kVcrBLYYs2dtl16ZMABjNy5lRNBrHHcfOS6RUiARYU-fg/viewform?usp=send_form&pli=1&authuser=0).
**Time required:** ~30 minutes for setup, plus registration wait time.
Make sure you have:
- a Linux or macOS host
- at least 2 vCPU, 2 GB RAM, and 20 GB disk; use [Infrastructure Sizing](/build/become-a-validator/infrastructure) before production
- PostgreSQL 15+
- Docker **or** Go 1.24+
- reliable HTTPS and WSS RPC endpoints for every assigned chain
- outbound TCP access to the Superform bootstrap peers on port `6690`
- access to the validator runtime artifacts you plan to use
```bash
nc -zv bootstrap-1.supervaults.superform.xyz 6690
nc -zv bootstrap-2.supervaults.superform.xyz 6690
```
If either check fails, fix firewall or routing before doing anything else.
The P2P port is raw TCP ragep2p traffic. Do **not** put Cloudflare or any other HTTP proxy in front of port `6690`.
```bash
echo $GITHUB_TOKEN | docker login ghcr.io -u --password-stdin
docker pull ghcr.io/superform-xyz/validator-network:latest
```
Use a pinned release tag in production when one is available.
```bash
git clone https://github.com/superform-xyz/supervaults-validator-network.git
cd supervaults-validator-network
make deps
make build
```
```bash
./bin/oracle-node keys generate --keystore ./keystore
```
This creates your validator identity and writes two artifacts in `./keystore`: `ocr2_.json` for the OCR2/offchain + onchain bundle and `p2p_.json` for the ragep2p identity. Keep both files at `0600` permissions and back up the keystore before continuing.
```bash
cp config.template.toml config.toml
cp chains.template.yaml chains.yaml
```
If you are using the published image, fetch `config.template.toml` and `chains.template.yaml` from the validator repo or your onboarding bundle before running those copy commands.
Fill in:
- your offchain and onchain keys if you are using direct hex
- the generated key file path if you are loading from `./keystore/ocr2_.json`
- the `kms_key_id` if you are using AWS KMS for the onchain signer
- PostgreSQL connection string
- Superform bootstrap peers
- RPC endpoints for each assigned chain
- the chain-specific `ecdsa_oracle_address` and `super_governor_address` values provided during onboarding
Use [Node Setup](/build/become-a-validator/node-setup) for the full install flow and [Configuration Reference](/build/become-a-validator/configuration-reference) for every field.
```bash
docker run -d \
--name oracle-node \
-v $(pwd)/config.toml:/app/config.toml:ro \
-v $(pwd)/chains.yaml:/app/chains.yaml:ro \
-v $(pwd)/keystore:/app/keystore:ro \
-p 6690:6690 -p 9090:9090 -p 8080:8080 \
ghcr.io/superform-xyz/validator-network:latest
docker logs -f oracle-node
```
Mount `./keystore` only when `config.toml` uses `key_file_path = "./keystore/ocr2_.json"`. Direct-hex configs do not need the extra volume, and KMS-backed setups still need the local offchain/P2P identity available to the container.
```bash
make setup-db
make run-local
```
Then verify:
```bash
curl -s localhost:8080/healthz
curl -s localhost:9090/metrics | grep ocr2_up
```
You want a healthy process before asking Superform to register it.
```bash
./bin/oracle-node keys export
```
Send Superform the public values only:
```json
{
"validator_name": "Your Organization",
"onchain_public_key": "04...",
"ethereum_address": "0x...",
"offchain_public_key": "...",
"config_encryption_public_key": "...",
"peer_id": "12D3KooW...",
"p2p_endpoint": "your-host.example.com:6690"
}
```
`keys export` gives Superform exactly what they need to build the onchain config: `validatorPublicKeys[]` gets your onchain public key, `validators[]` gets the derived Ethereum address, and `OracleIdentity[]` gets your `peer_id`, `offchain_public_key`, and `config_encryption_public_key`. Never send private keys. For the full registration payload shape, validation invariants, and failure-mode debugging, use [Operations](/build/become-a-validator/operations).
After Superform calls `SuperGovernor.setValidatorConfig()`, look for:
```text
Received ValidatorConfigSet event blockNumber=... chainID=1
Config loaded successfully configDigest=0x... signers=N F=...
```
Then, within the next round:
```text
INFO Starting PPS observation epoch=1 round=1
INFO Observation complete vaultsObserved=N
```
If your node never reaches these logs, go straight to [Operations](/build/become-a-validator/operations).
## Exit criteria
- [ ] Bootstrap peers reachable on TCP `6690`
- [ ] `config.toml` and `chains.yaml` filled with real values
- [ ] `/healthz` returns healthy
- [ ] `ocr2_up` is exposed in Prometheus metrics
- [ ] Public key bundle submitted to Superform
- [ ] `Config loaded successfully` appears after registration
- [ ] First OCR2 observation logs appear
---
# Infrastructure Sizing
Source: https://docs.superform.xyz/build/become-a-validator/infrastructure
Use this page to choose the infrastructure tier for a Superform validator. The node is lightweight: it observes SuperVault PPS values, participates in OCR2 consensus, signs reports, and persists round state.
It does **not** run an archive node, index chains, or replace your RPC provider. In production, most operator cost comes from RPC endpoints, managed Postgres, monitoring, and signing-key custody rather than the validator binary itself.
## How much infrastructure do validators need?
For most approved operators, start with the recommended baseline:
- 2 vCPU / 2 GB RAM validator host
- static public endpoint for ragep2p on TCP `6690`
- managed PostgreSQL 15+
- production RPC provider coverage for every assigned chain
- KMS-backed onchain signing where supported
- Prometheus-compatible metrics, alerts, and basic logs
The validator can run on AWS, GCP, Azure, Hetzner, OVH, or bare metal. Pick the platform you can operate reliably. A cheap VPS with weak backups, no alerting, and a rotating IP is worse than a slightly more expensive managed setup.
## Recommended production baseline
Reference production deployment for a single validator:
| Component | Recommended baseline |
|-----------|----------------------|
| Compute | 1 validator host, 2 vCPU ARM/x86, 2 GB RAM |
| Disk | 16–20 GB encrypted SSD |
| Public networking | Static IP or stable DNS name; direct TCP `6690` reachability |
| Database | Managed PostgreSQL 15+, single-AZ, 20 GB, TLS, 7-day backups |
| Key custody | KMS-backed secp256k1 onchain signer; local or keystore-managed OCR2/P2P identities |
| Monitoring | Prometheus metrics, Grafana dashboards/alerts, log retention |
| RPC | Paid HTTPS + WSS endpoints for each assigned chain |
A reference AWS deployment maps this to one `t4g.small` validator host in `eu-west-1`, a 16 GB encrypted gp3 root disk, one Elastic IP, RDS PostgreSQL 16 on `db.t4g.micro` with TLS and deletion protection, an AWS KMS secp256k1 CMK for the onchain signer, AWS Managed Prometheus/Grafana, and alert routing through SNS or an on-call channel. The validator, optional `snapshotd` IPC sidecar, and metrics collector can run as separate containers on the same host, with localhost-only Prometheus endpoints.
This baseline is enough because the node is mostly stateless outside Postgres. OCR2 round state is small and recoverable; RPC quality and network reachability matter more than raw CPU.
## Sizing tiers
| Component | Minimum | Recommended | High-Availability |
|-----------|---------|-------------|-------------------|
| Use case | Testnet, staging, evaluation | Production validator | Operators with SLA requirements |
| Node CPU | 2 vCPU | 2 vCPU | 4 vCPU |
| Node RAM | 2 GB | 2 GB | 4–8 GB |
| Node disk | 20 GB SSD | 16–20 GB encrypted SSD | 20+ GB encrypted SSD |
| Architecture | x86_64 or arm64 | arm64 preferred | arm64 preferred |
| Public IP | Dynamic acceptable for tests | Static IP / stable DNS | Static IP / stable DNS with failover plan |
| Postgres | Local or self-hosted | Managed single-AZ, 20 GB | Managed multi-AZ, 20–50 GB |
| Key custody | Local encrypted keystore | Cloud KMS for onchain key | Cloud KMS or HSM-backed signer |
| Metrics | Local Prometheus | Prometheus + Grafana alerts | Managed metrics + paging |
| Backups | Manual | 7-day database backups | 7-day backups + cross-region snapshot policy |
| Network | Non-residential preferred | Datacenter network, 100 Mbps+ | Datacenter network, 1 Gbps / SLA-backed |
Minimum is not a production recommendation. Use it to evaluate the software, not to operate a live validator with meaningful stake or uptime expectations.
## AWS cost estimate
Approximate AWS `eu-west-1` on-demand reference pricing for the recommended baseline. These are maintainable planning numbers, not a quote. They exclude Savings Plans, Reserved Instances, taxes, support plans, and RPC provider fees.
| Item | Reference spec | Approx. monthly cost |
|------|----------------|----------------------|
| EC2 `t4g.small` | 730 h × public on-demand rate | ~$12 |
| EBS gp3 root disk | 16 GB encrypted gp3 | ~$1.5 |
| RDS PostgreSQL | `db.t4g.micro`, single-AZ | ~$13 |
| RDS storage + backups | 20 GB gp3 + 7-day backups | ~$3 |
| AWS Managed Prometheus | ~10M allowlisted samples/month | ~$9 |
| KMS key + signing API calls | 1 asymmetric secp256k1 CMK | ~$1–3 |
| Secrets Manager | ~5 secrets | ~$2 |
| ECR / SSM / CloudWatch logs | Light operational usage | ~$1–3 |
| Data transfer | ragep2p + RPC client traffic | ~$2–5 |
| **Total before RPC fees** | Single validator | **~$45–55/month** |
RPC fees are separate and may be the largest operating line item. Budget for reliable HTTPS and WSS endpoints on every assigned chain through Alchemy, QuickNode, or an equivalent provider.
Adding a separate optional snapshot service host typically adds about **$20–25/month** for another small EC2 host, EIP, and extra metric ingestion. Redis or other cache costs depend on the deployment shape and whether the service is shared.
## Cloud and bare-metal equivalents
Rough equivalents for the recommended tier:
| Provider | Compute | Database |
|----------|---------|----------|
| AWS | `t4g.small` | RDS PostgreSQL `db.t4g.micro`, single-AZ |
| GCP | `t2a-standard-1` or small x86 VM | Cloud SQL PostgreSQL small shared/burstable tier |
| Azure | `B2pls v2` or comparable 2 vCPU VM | Azure Database for PostgreSQL burstable tier |
| Hetzner Cloud | `CAX11` or comparable 2 vCPU VM | Managed Postgres if available, otherwise self-hosted with backups |
| OVHcloud | Small 2 vCPU public cloud or ARM bare-metal host | Public Cloud Database for PostgreSQL |
| Bare metal | Any reliable 2 vCPU / 2 GB server with SSD | Self-hosted PostgreSQL 15+ with tested backups |
Hetzner, OVH, and bare-metal deployments can be cheaper than AWS. The tradeoff is operational ownership: backups, monitoring, failover, OS patching, network reliability, and key custody become your responsibility.
## Optional snapshot service
Some vaults need a price provider for cross-asset PPS conversion, such as Pendle, Spectra, or staking-style vaults that report shares in a different unit than the deposit asset.
When those vaults are assigned, operators may run `snapshotd` as either:
- **IPC sidecar**: same host as the validator, communicating over a Unix socket. Lowest cost and simplest when one validator consumes it.
- **Standalone HTTP service**: separate host with JWT-gated HTTPS, useful when multiple validators or services share one snapshot endpoint.
Both modes can share the same Redis-compatible cache for vault metadata, so a validator restart does not need to re-fetch every vault domain separator.
If all assigned vaults are standard ERC-4626 and do not require cross-asset conversion, the validator can read directly from configured vault contracts and the snapshot service may not be needed.
## What to read next
Fast end-to-end checklist once you are approved.
Install the node, configure runtime files, and verify startup.
Full `config.toml`, `chains.yaml`, KMS, and OCR2 timing reference.
Health endpoints, priority metrics, alerts, and operating habits.
---
# Node Setup
Source: https://docs.superform.xyz/build/become-a-validator/node-setup
This is the canonical setup guide. Use it when you need the full install flow, not just the short checklist. Size production hosts first with [Infrastructure Sizing](/build/become-a-validator/infrastructure).
## Before you install
Make sure you already have:
- approval to join the permissioned validator program
- the bootstrap peer entries supplied by Superform
- the chain-specific contract addresses and current config version for your deployment
- reliable HTTPS and WSS RPC endpoints for each assigned chain
- PostgreSQL 15+
If you do not have those yet, stop and get them first. The software is the easy part.
## System requirements
| Requirement | Baseline |
|------------|----------|
| OS | Linux or macOS |
| CPU | 2 vCPU minimum |
| RAM | 2 GB minimum, 4 GB recommended |
| Disk | 20 GB minimum |
| Database | PostgreSQL 15+ |
| Network | Stable outbound connectivity, direct TCP `6690`, and reliable uptime |
## Choose a deployment method
Recommended for most operators. No Go toolchain required.
Use this if you need direct source control over the runtime.
Useful when validating local code changes before deployment.
## Published Image
Images are published to GitHub Container Registry:
```text
ghcr.io/superform-xyz/validator-network
```
### 1. Authenticate to GHCR
```bash
echo $GITHUB_TOKEN | docker login ghcr.io -u --password-stdin
```
You need a GitHub token with `read:packages`.
### 2. Pull the image
```bash
docker pull ghcr.io/superform-xyz/validator-network:latest
# or pin a versioned release tag when available
docker pull ghcr.io/superform-xyz/validator-network:v1.2.3
```
Prefer pinned tags in production.
### 3. Prepare local config files
```bash
cp config.template.toml config.toml
cp chains.template.yaml chains.yaml
cp .env.template .env
```
If you do not have the templates locally, get them from the validator repository or your onboarding bundle before continuing.
### 4. Fill in the required values
In `config.toml`, set:
- `[keys]` offchain private key for direct-hex or KMS-backed setups, or `key_file_path` if you are loading from `./keystore/ocr2_.json`
- `[keys.onchain_private_key]` dev private key or production `kms_key_id`
- `[database].url`
- `[p2p].bootstrap_peers`
- optional `announce_addresses` if you are behind NAT
- `chain_config_path`
In `chains.yaml`, every assigned chain needs both a reliable HTTPS RPC endpoint for reads/tx submission and a WSS endpoint for event subscriptions. Do not treat WSS as optional.
In `chains.yaml`, set the RPC endpoints and the contract addresses provided by Superform.
### 5. Run the container
```bash
docker run -d \
--name oracle-node \
-v $(pwd)/config.toml:/app/config.toml:ro \
-v $(pwd)/chains.yaml:/app/chains.yaml:ro \
-v $(pwd)/keystore:/app/keystore:ro \
-p 6690:6690 -p 9090:9090 -p 8080:8080 \
ghcr.io/superform-xyz/validator-network:latest
```
If `config.toml` uses `key_file_path = "./keystore/ocr2_.json"`, the `./keystore` mount is mandatory because the node also expects `./keystore/p2p_.json` beside it. Direct-hex configs do not need the mount. KMS only replaces the onchain signer, so you still need either direct offchain hex or the local keystore/P2P files.
### 6. Verify basic health
```bash
docker logs -f oracle-node
curl -s localhost:8080/healthz
curl -s localhost:9090/metrics | grep ^ocr2_ | head -20
```
## Build from Source
### 1. Clone the repository
```bash
git clone https://github.com/superform-xyz/supervaults-validator-network.git
cd supervaults-validator-network
```
### 2. Install dependencies
```bash
make deps
```
### 3. Set up PostgreSQL
```bash
make setup-db
```
### 4. Generate your keys
```bash
./bin/oracle-node keys generate --keystore ./keystore
```
Back up the keystore immediately.
### 5. Create configuration files
```bash
cp config.template.toml config.toml
cp chains.template.yaml chains.yaml
```
Populate the same runtime values listed in the container workflow above.
### 6. Build and run
```bash
make build
make run-local
```
For more verbose logs:
```bash
./bin/oracle-node --config config.toml --log-level debug
```
## Docker (Build Locally)
Use this when testing local code changes.
### 1. Clone the repository
```bash
git clone https://github.com/superform-xyz/supervaults-validator-network.git
cd supervaults-validator-network
```
### 2. Prepare the local Docker environment
```bash
make docker-setup-local
```
### 3. Configure secrets and runtime values
```bash
nano .env
nano config.toml
nano chains.yaml
```
### 4. Start services
```bash
make run-docker
```
### 5. Useful lifecycle commands
```bash
make docker-logs
make docker-down
make docker-clean
```
---
## Optional snapshot service
Some assigned vaults may require `snapshotd` for cross-asset PPS conversion. If Superform tells you to run it, deploy it either as:
- an IPC sidecar on the validator host using a Unix socket, or
- a standalone HTTPS service protected by JWT when multiple validators or services share it.
Skip it unless your assigned vault set requires it. Standard ERC-4626 vaults can be observed directly from configured contracts.
## Key management rules that matter during setup
- Generate keys once per validator identity.
- Back up the keystore before first production use.
- Never commit `keystore/`, `.env`, or `config.toml`.
- Use AWS KMS for the onchain signing key in production if supported by your environment.
- KMS only covers the onchain signer; the OCR2/offchain key and P2P identity remain independently managed.
- If you use key files, keep `ocr2_.json` and `p2p_.json` at `0600` permissions.
## Bootstrap peers
Both Superform bootstrap peers must be configured in `[p2p].bootstrap_peers`. Your node uses them to join the mesh and discover the rest of the validator set.
Those peer endpoints must resolve directly to the validator hosts. Cloudflare proxying or any other HTTP proxy on the P2P port will break ragep2p connectivity.
The peer IDs are expected to remain stable across normal infrastructure replacements. If Superform ever rotates one, treat that as a real config update and replace the entry before your next restart.
Networking rule that matters in production:
- outbound TCP `6690` is mandatory so your node can reach the Superform bootstrap peers and the wider mesh
- inbound TCP `6690` is required when other validators must dial the endpoint you advertise directly
- outbound-only can work in some NATed topologies if your announced endpoint is still reachable through another path, but do not treat that as the default production assumption
If you are behind NAT or a load balancer, set `announce_addresses` so other validators can dial you back correctly.
Verify connectivity before startup:
```bash
nc -zv bootstrap-1.supervaults.superform.xyz 6690
nc -zv bootstrap-2.supervaults.superform.xyz 6690
```
## Healthy startup sequence
On a clean boot you should see a progression close to:
```text
INFO Starting SuperVaults PPS Oracle Node
INFO Connecting to database...
INFO Database connected
INFO Loading keystore...
INFO Keystore loaded
INFO Creating OCR2 oracle...
INFO OCR2 oracle instance created successfully
INFO Starting OCR2 oracle...
INFO OCR2 oracle started successfully
INFO Oracle node started successfully
```
## What success looks like before registration
Before Superform registers your node, you should already have:
- a healthy process
- successful database connectivity
- working `/healthz` and `/metrics` endpoints
- no local config parse errors
- clean outbound connectivity to the bootstrap peers and your RPC providers
Once that is true, move to [Operations](/build/become-a-validator/operations) for registration and production runbook tasks.
---
# Operations
Source: https://docs.superform.xyz/build/become-a-validator/operations
This page is the operator runbook. It covers what happens after installation: registration, steady-state checks, upgrades, key rotation, and incident response.
## First production checklist
Once the node is locally healthy, complete these steps in order.
### 1. Export your public keys
```bash
./bin/oracle-node keys export
```
You need these public values:
- onchain public key
- derived Ethereum address
- offchain public key
- config encryption public key
- peer ID
- public P2P endpoint
`keys export` is converting your local key material into the values Superform inserts into the live validator config: `validatorPublicKeys[]`, `validators[]`, and the ABI-encoded `OracleIdentity[]` entries inside `offchainConfig`.
### 2. Self-validate the bundle
Before sending anything to Superform, verify:
- `onchain_public_key` is 65 bytes / 130 hex chars and starts with `04`
- `offchain_public_key` is 32 bytes / 64 hex chars
- `config_encryption_public_key` is 32 bytes / 64 hex chars
- the exported Ethereum address matches the onchain public key
- `peer_id` is a valid libp2p base58 string, typically starting with `12D3Koo`
- `p2p_endpoint` resolves publicly to the host that is actually serving your node on TCP `6690`
- your node is reachable on the endpoint you plan to advertise
- if you are behind NAT or a load balancer, `announce_addresses` is set to the same public `host:port` you plan to submit
Use the smallest real preflight instead of guessing:
```bash
nc -zv 6690
```
From another machine or network if possible. Do not put Cloudflare or any other HTTP proxy in front of this port.
### 3. Submit the public key bundle
Send the bundle to your Superform onboarding contact. Never send private keys.
```json
{
"validator_name": "Your Organization",
"onchain_public_key": "04...",
"ethereum_address": "0x...",
"offchain_public_key": "...",
"config_encryption_public_key": "...",
"peer_id": "12D3KooW...",
"p2p_endpoint": "your-validator.example.com:6690"
}
```
Mapping reminder:
- `onchain_public_key` → `validatorPublicKeys[]`
- `ethereum_address` → `validators[]`
- `peer_id` → `OracleIdentity.peerID`
- `offchain_public_key` → `OracleIdentity.offchainPublicKey`
- `config_encryption_public_key` → `OracleIdentity.configEncryptionPublicKey`
### 4. Wait for onchain registration
During Phase 1, Superform calls `SuperGovernor.setValidatorConfig()` on your behalf. That payload has five parts:
- `version` — must be strictly newer than the previous config
- `validators[]` — one Ethereum address per validator
- `validatorPublicKeys[]` — the matching 65-byte uncompressed ECDSA public keys
- `quorum` — the report threshold for the validator set size
- `offchainConfig` — the ABI-encoded `OracleIdentity[]` entries for the same validators
For each validator, `validators[i]`, `validatorPublicKeys[i]`, and `OracleIdentity[i]` all have to describe the same operator identity. In practice, the config only loads cleanly when:
- all three arrays have the same length
- `validators[i]` is the Ethereum address derived from `validatorPublicKeys[i]`
- `OracleIdentity.validatorPublicKey` byte-matches the same `validatorPublicKeys[i]`
- the offchain and config encryption keys are both 32 bytes
Most registration failures reduce to one of three things: array misalignment, address/public-key mismatch, or malformed `offchainConfig` bytes. If your node sees the event but refuses to load the config, reason about the failure in that order first.
Once the transaction confirms, your node should pick up the new validator set automatically.
### 5. Confirm the node joined the active config
Look for logs like:
```text
Received ValidatorConfigSet event blockNumber=... chainID=1
Config loaded successfully configDigest=0x... signers=N F=...
```
Within the next round, you should also see observation activity.
Example healthy progression:
```text
INFO Starting PPS observation epoch=1 round=1
INFO Observation complete vaultsObserved=N
```
---
## Steady-state health checks
These are the minimum checks worth automating.
### Local endpoints
```bash
curl -s localhost:8080/healthz
curl -s localhost:8080/readyz
curl -s localhost:9090/metrics | grep ^ocr2_
```
### Metrics that matter most
- `ocr2_up`
- `ocr2_strategy_last_update_seconds`
- `ocr2_strategy_update_stale`
- `ocr2_transmit_total{status}`
- `ocr2_plugin_observations_total{result}`
- `ocr2_config_version`
- `ocr2_config_signers`
If `ocr2_strategy_last_update_seconds` stops advancing or `ocr2_strategy_update_stale` flips to `1`, treat it as a real incident.
---
## Key rotation
Rotate keys when compromise is suspected, your custody model changes, or Superform asks you to re-register.
```bash
./bin/oracle-node keys generate --keystore ./keystore-new
```
```bash
./bin/oracle-node keys export --keystore ./keystore-new
```
Superform must publish a new validator config onchain before the rotation is active.
Replace the relevant private key values in `config.toml`, or update your KMS key reference.
Restart only after the updated config is ready to use.
## Upgrades
### Source deployment
```bash
git pull origin main
make build
make run-local
```
### Docker deployment
Pull the target image tag, then restart the container or compose stack.
```bash
docker pull ghcr.io/superform-xyz/validator-network:latest
```
After any upgrade, verify:
- the process starts cleanly
- `/healthz` and `/metrics` work
- `ocr2_config_version` is unchanged unless the network intentionally rotated config
- observations and transmissions resume
---
## Common failure modes
### Database connection failure
```text
Error: failed to initialize database: failed to ping database
```
Check PostgreSQL availability and the `[database].url` value.
### Keys not found
```text
Error: failed to load OCR2 key bundle
```
Generate keys or fix the configured path / embedded key material.
### Bootstrap peers unreachable
If the node never participates, verify:
1. outbound TCP `6690` is open
2. `bootstrap_peers` matches the current values from Superform
3. `announce_addresses` is set correctly if you are behind NAT
4. debug logs do not show repeated ragep2p dial failures
### Config digest mismatch
If your `configDigest` differs from the rest of the network, check:
- `config_version` in `config.toml`
- whether you observed the same `ValidatorConfigSet` event as the rest of the network
- whether Superform submitted aligned `validators[]`, `validatorPublicKeys[]`, and `OracleIdentity[]` arrays
- whether `validators[i]` still derives from the paired `validatorPublicKeys[i]` and byte-matches the paired `OracleIdentity.validatorPublicKey`
- whether your local binary matches the expected network release
### Observations succeed but no onchain transmissions land
Check:
1. `ocr2_transmit_total{status="failure"}` and `{status="error"}`
2. whether the registered transmitter address matches your onchain key
3. RPC health on the transmit chain
4. debug logs for revert or nonce mismatch details
---
## Error reference
| Error | Cause | Action |
|-------|-------|--------|
| `validator N (pubkey ...) not found in offchainConfig` | Onchain/offchain identity mismatch | Re-export keys and ask Superform to republish the config |
| `derived F=N from quorum=N is invalid: F*3=N must be < N` | Invalid quorum for the validator count | Superform must adjust the config |
| `failed to unpack offchain config` | Malformed `offchainConfig` bytes | Superform must republish the config |
| `no ValidatorConfigSet events found` | Registration has not happened yet | Wait for Superform confirmation |
| `could not import github.com/smartcontractkit/libocr` | Dependencies missing in source build | Run `make deps` |
Full config, metrics, health endpoint, and alerting reference.
---
# Configuration Reference
Source: https://docs.superform.xyz/build/become-a-validator/configuration-reference
Use this as the technical reference for validator configuration. For setup flow, use [Node Setup](/build/become-a-validator/node-setup). For troubleshooting and operator procedures, use [Operations](/build/become-a-validator/operations). For health endpoints and alerts, use [Monitoring](/build/become-a-validator/monitoring).
## config.toml
Copy `config.template.toml` to `config.toml` and fill in your values.
### `[node]`
```toml
[node]
node_id = "oracle-1" # Unique identifier used in logs
p2p_listen_addr = "0.0.0.0" # Address to listen on for P2P connections
p2p_port = 6690 # TCP port for ragep2p
```
### `[keys]`
Choose one provisioning pattern.
**Option 1 — direct hex**
```toml
[keys]
offchain_private_key = "76918f97bfe8..."
[keys.onchain_private_key]
private_key = "ed6ca3d3ac17..."
```
Use this when you want explicit control over the key material. The offchain key is Ed25519 hex, while the dev/testing onchain key is a secp256k1 private key.
The config encryption key is derived automatically from your offchain key. Do not try to configure it separately in `config.toml`.
**Option 2 — key file**
```toml
[keys]
key_file_path = "./keystore/ocr2_.json"
```
Use this when you want the node to read the OCR2/offchain bundle from disk. The companion P2P identity must live beside it as `./keystore/p2p_.json`, so mount the whole `./keystore` directory into the container if you run Docker. Keep both files at `0600` permissions.
**Option 3 — AWS KMS for the onchain key**
```toml
[keys]
offchain_private_key = "76918f97bfe8..."
[keys.onchain_private_key]
kms_key_id = "arn:aws:kms:us-east-1:123456789:key/abcd-1234"
```
Use this when you want the offchain/OCR2 identity managed separately from the production onchain signer. Supported KMS identifiers: full ARN, alias, or UUID.
The KMS key must be an asymmetric signing key of type `ECC_SECG_P256K1`. In production, authorize only the validator runtime role to call `kms:Sign`. Keep admin principals able to manage the key, but do not grant them signing rights unless they also operate the validator. This keeps operational administration separate from report-signing authority.
### `[p2p]`
```toml
[p2p]
bootstrap_peers = [
"12D3KooWGc9hJKBWks3oq9e7H4oZseoncbrxcZQfkW5NTBH473un@bootstrap-1.supervaults.superform.xyz:6690",
"12D3KooWCDXcCoE86B4dfMg5hXpoPr6xj4wGRaYqxHbkgPZW3yer@bootstrap-2.supervaults.superform.xyz:6690",
]
# announce_addresses = ["203.0.113.42:6690"]
```
Set `announce_addresses` if your node listens on one address but other validators must dial a different public endpoint.
Do not front the P2P port with Cloudflare or any HTTP proxy. ragep2p expects direct TCP reachability.
### `[database]`
```toml
[database]
url = "postgresql://oracle:password@localhost:5432/oracle_db?sslmode=disable"
max_connections = 10
max_idle_conns = 5
conn_max_lifetime = 3600
```
### `[monitoring]`
```toml
[monitoring]
metrics_port = 9090
health_port = 8080
log_level = "info"
log_format = "json"
health_check_interval = "1h"
max_pps_deviation_ppb = 10000000
```
### `[ocr2]`
```toml
[ocr2]
config_version = 3
# config_sync_chain_id = 1
```
`config_sync_chain_id` defaults to Ethereum mainnet (`1`). This is the chain where `SuperGovernor` emits validator config updates, so every node must watch the same source chain.
All nodes must use the same `config_version`. A mismatch produces a different config digest and prevents consensus.
### `chain_config_path`
```toml
chain_config_path = "./chains.yaml"
```
---
## chains.yaml
`chains.yaml` maps chain ID to the chain configuration your node should observe.
```yaml
1:
name: ethereum
chain_id: 1
https_rpc: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
wss_rpc: "wss://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
block_time: 12s
supported_strategies:
- "0xVault1Address"
- "0xVault2Address"
ecdsa_oracle_address: "0x..."
super_governor_address: "0x..."
```
### Field reference
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Human-readable chain label used in logs and metrics. |
| `chain_id` | integer | EVM chain ID; must match the top-level map key. |
| `https_rpc` | string | HTTPS RPC endpoint for reads and tx submission. |
| `wss_rpc` | string | WebSocket RPC endpoint for event subscriptions. |
| `block_time` | duration | Expected block time in Go duration format. |
| `supported_strategies` | string[] | Vault addresses this node will observe. |
| `ecdsa_oracle_address` | string | `ECDSAPPSOracle` contract address supplied during onboarding. |
| `super_governor_address` | string | `SuperGovernor` contract address supplied during onboarding. |
### RPC requirements
- HTTPS is required on every configured chain.
- WSS is required for event subscriptions.
- Public rate-limited endpoints are not suitable for production.
### Scope rules
Only include chains that Superform assigned to your validator deployment and for which you have reliable RPC access. A broken chain config creates observation errors for every strategy on that chain.
Typical supported deployments include Ethereum, Base, Arbitrum, Optimism, and Polygon, but only configure the chains Superform assigned to your validator.
---
## OCR2 timing presets
The versioned `config/vN.json` files define the OCR2 timing profile. Every validator in the network must use the same version.
### v2 — high-frequency testing
| Parameter | Value |
|-----------|-------|
| `delta_round` | 2s |
| `delta_progress` | 120s |
| `delta_resend` | 30s |
| `delta_grace` | 5s |
| `delta_stage` | 10s |
| `r_max` | 3 |
| `max_stale_duration` | 5m |
### v3 — hourly production default
| Parameter | Value |
|-----------|-------|
| `delta_round` | 30m |
| `delta_progress` | 10m |
| `delta_resend` | 5m |
| `delta_grace` | 1m |
| `delta_stage` | 2m |
| `r_max` | 5 |
| `max_stale_duration` | 5m |
### Freshness controls
| Layer | Config location | Purpose |
|------|------------------|---------|
| Round cadence | `delta_round` | How often the protocol attempts a report |
| Report freshness | `max_stale_duration` | Rejects reports that finalize too late |
| Node health alerting | `health_check_interval` | Warns when no onchain confirmation arrives |
| Contract enforcement | `maxStaleness` | Auto-pauses a strategy if updates stop |
For the production `v3` profile, that contract-side `maxStaleness` window is designed around a 24-hour safety boundary even though rounds attempt far more frequently.
### Switching versions
Update `config_version` on all nodes and restart them in a coordinated window. Mixed versions split the network until every validator converges on the same config digest.
---
# Monitoring
Source: https://docs.superform.xyz/build/become-a-validator/monitoring
Use this page when you need validator observability and alerting guidance. For config fields, use [Configuration Reference](/build/become-a-validator/configuration-reference). For operator procedures, use [Operations](/build/become-a-validator/operations).
## Health endpoints
Both ports are configured under `[monitoring]` in `config.toml`.
| Endpoint | Port | Purpose |
|----------|------|---------|
| `GET /metrics` | `metrics_port` | Prometheus scrape target |
| `GET /healthz` | `health_port` | Process + DB liveness |
| `GET /readyz` | `health_port` | Same checks today; reserved for future readiness gates |
Quick check:
```bash
curl -s localhost:8080/healthz
curl -s localhost:8080/readyz
curl -s localhost:9090/metrics | grep ^ocr2_ | head -20
```
## Priority signals
If you only alert on a handful of metrics, start here:
- `ocr2_up` — process liveness
- `ocr2_strategy_last_update_seconds` — confirms PPS updates are still landing onchain
- `ocr2_strategy_update_stale` — fastest signal that a strategy has stopped updating
- `ocr2_transmit_total` labeled by `status` — shows whether transmit attempts are succeeding
- `ocr2_plugin_observations_total` labeled by `result` — reveals RPC or observation failures
- `ocr2_config_version` and `ocr2_config_signers` — confirm the node loaded the expected active network config
## Metrics reference
### Onchain transmission health
- `ocr2_strategy_last_update_seconds` (labels: `chain_id`, `strategy`) — gauge with the Unix timestamp of the latest observed `PPSValidated` event
- `ocr2_strategy_update_stale` (labels: `chain_id`, `strategy`) — gauge set to `1` when no `PPSValidated` event arrives within `health_check_interval`
### Transmitter
- `ocr2_transmit_total` (labels: `chain_id`, `status`) — counter of transmission attempts by outcome
- `ocr2_transmit_strategies_total` (label: `chain_id`) — counter of strategies packed into submitted transactions
- `ocr2_transmit_pack_errors_total` (label: `chain_id`) — counter of report packing failures
- `ocr2_transmit_duration_seconds` (label: `result`) — histogram of end-to-end transmit latency
### Plugin phases
- `ocr2_plugin_phase_duration_seconds` (labels: `phase`, `result`) — histogram of phase latency
- `ocr2_plugin_phase_total` (labels: `phase`, `result`) — counter of phase invocation outcomes
- `ocr2_plugin_observations_total` (labels: `chain_id`, `result`) — counter of per-strategy observation outcomes
- `ocr2_plugin_insufficient_observations_total` (label: `chain_id`) — counter of vaults dropped because quorum observations were not available
## Alerting baseline
For production, alert on the smallest set of signals that prove the node can observe, participate, and transmit:
| Signal | Suggested action |
|--------|------------------|
| `ocr2_up` missing or `0` | Page or notify the operator immediately |
| `ocr2_strategy_update_stale == 1` | Treat as an incident; check RPC, config, and transmission logs |
| `ocr2_transmit_total{status="failure"}` increasing | Investigate transmitter key, RPC health, gas, and contract reverts |
| `ocr2_plugin_observations_total{result="error"}` increasing | Check per-chain RPC health and vault configuration |
| Unexpected `ocr2_config_version` | Confirm the node loaded the intended onchain config |
Email, Slack, or SNS-style notifications are enough for most operators. Operators with SLA requirements should route these alerts into PagerDuty, Opsgenie, or an equivalent on-call system.
## Recommended operating habit
After every config change, restart, or upgrade, verify three things in this order:
1. `/healthz` is healthy
2. `ocr2_config_version` and `ocr2_config_signers` match expectations
3. `ocr2_strategy_last_update_seconds` continues advancing
---
# How It Works
Source: https://docs.superform.xyz/build/become-a-validator/how-it-works
This page explains the protocol. It does not repeat setup or runbook instructions. For the rollout model and validator incentives, use [Economics](/build/become-a-validator/economics).
## The problem validators solve
SuperVaults rely on price-per-share (PPS) updates to value positions correctly. A single trusted updater would be a central point of failure. The validator network replaces that with an OCR2 quorum: multiple operators observe PPS independently, agree offchain, and submit one signed report onchain.
## Core components
- **Validator nodes** observe PPS values on assigned chains.
- **OCR2** coordinates offchain consensus.
- **`ECDSAPPSOracle`** accepts quorum-backed PPS reports onchain.
- **`SuperGovernor`** is the source of truth for validator membership and network config.
## OCR2 round flow
A production `v3` deployment attempts a round every 30 minutes.
```text
Leader selected
│
├─ Query Leader broadcasts round context
├─ Observation Validators fetch PPS independently
├─ Report Leader aggregates observations
├─ Finalize Leader shares the candidate report
├─ Accept Validators validate freshness and safety
├─ Sign Validators sign the finalized report
└─ Transmit Designated transmitter posts onchain
```
An epoch is a group of rounds led by the same leader. After `r_max` rounds, or when the leader stalls for longer than `delta_progress`, OCR2 advances to a new epoch and deterministically rotates leadership.
## What your node does
### Observation
Your node calls `pricePerShare()` for each configured strategy, on each configured chain, using the RPC endpoints in `chains.yaml`.
That observation is packaged for OCR2 messaging and signed with your offchain identity before it is shared across the validator mesh.
### Report assembly
If your node is the round leader, it:
1. filters invalid or missing observations
2. requires enough valid observations to satisfy quorum
3. computes the median PPS per vault
4. builds the ABI-encoded report payload
If a vault does not receive enough valid observations, it is dropped from that round instead of poisoning the whole report.
### Acceptance checks
Before signing, every validator checks:
- report freshness against `max_stale_duration`
- optional deviation guard versus its own observation
- whether the onchain nonce changed before acceptance
### Transmission
The designated transmitter submits the report to `ECDSAPPSOracle.transmit()`. If quorum is valid, the contract updates PPS and emits `PPSValidated`.
Onchain verification checks the submitted validator signatures against the registered signer set before the PPS update is accepted.
## Quorum and fault tolerance
The network is configured so it can tolerate a bounded number of faulty validators while still producing correct results.
For OCR2 this is the Byzantine constraint that matters: with `N` validators and fault tolerance `F`, the config must satisfy `F * 3 < N`. In practice Superform chooses an odd quorum so `F = (quorum - 1) / 2`, then builds the validator set around that limit.
| Validators (`N`) | Quorum | Max faulty (`F`) |
|:-:|:-:|:-:|
| 4 | 3 | 1 |
| 7 | 5 | 2 |
| 10 | 7 | 3 |
| 13 | 9 | 4 |
If fewer than `quorum` validators sign a round, that round does not produce an onchain report.
## How validator identity maps into the config
Every validator contributes one aligned identity across three places in the active config:
- `validators[i]` is the Ethereum address derived from that validator's onchain public key
- `validatorPublicKeys[i]` is the 65-byte uncompressed ECDSA public key used for onchain signature verification
- `OracleIdentity[i]` carries the same validator's `validatorPublicKey`, `peerID`, `offchainPublicKey`, and `configEncryptionPublicKey`
Those arrays are not loosely related metadata. They are positional invariants. If the same validator is not represented at the same index across all three, config loading breaks.
## What happens when something breaks
### One validator is offline
Consensus still proceeds as long as the network remains within its fault-tolerance threshold.
### The leader is offline
Other validators wait for `delta_progress`, then a new epoch starts with a new leader.
### Too many validators are unavailable
The network cannot reach quorum. Rounds fail, `PPSValidated` stops advancing, monitoring turns stale, and strategies eventually hit onchain `maxStaleness` protections.
## Config digests
Every node independently derives a config digest from the active validator set, their keys, quorum, and OCR2 timing parameters.
If your digest differs from the rest of the network, the usual causes are:
- mismatched `config_version`
- observing a different `ValidatorConfigSet` event
- running against the wrong deployment inputs
That is an operational issue, not a protocol mystery. Fix the inputs and the digest should converge.
---
# Economics
Source: https://docs.superform.xyz/build/become-a-validator/economics
Validator economics are not final yet. Phase 1 is permissioned and does not include the full staking and slashing model described here.
This page covers the validator incentive model and rollout path. For protocol mechanics, use [How It Works](/build/become-a-validator/how-it-works).
## Why validators get paid
Validators provide the PPS updates SuperVaults need to function safely. If updates stop or become dishonest, vault operations degrade quickly. Rewards exist to keep high-quality operators online and aligned with the network.
## Rewards
Vault operators pay upkeep fees in **$UP** for active oracle coverage. Those fees are distributed to participating validators.
The intended model is:
- more stake increases reward weight
- better participation preserves reward eligibility
- missed rounds reduce your effective payout
Exact thresholds and rates are governance parameters and have not been published yet.
## Staking
Phase 2 is expected to require validators to bond **$UP**.
That bond is intended to:
- serve as collateral for slashing
- weight participation and rewards
- enforce a minimum commitment to stay active
## Slashing
Slashing is designed to penalize two categories of failure:
- **misreporting** — signing materially incorrect or invalid PPS data
- **insufficient participation** — staying below required uptime / contribution thresholds
Exact slash sizes and observation windows are still pending.
## Insurance fund
A portion of validator economics is expected to seed an insurance fund intended to backstop depositors if oracle failures cause verified harm.
It is not meant to cover:
- unrelated smart contract bugs
- strategy market losses
- immaterial losses below a governance-defined threshold
## Rollout phases
| Phase | Model | Status |
|-------|-------|--------|
| Phase 1 | Permissioned validator set | Live |
| Phase 2 | Open bonding, slashing, insurance fund seeding | Planned |
| Phase 3 | Governance-managed validator rotation | Planned |
## Practical takeaway for operators
Treat the current program as operationally serious even before the full economic model is live. Phase 1 may be permissioned, but uptime, correctness, and incident response still matter because the vaults depend on your node behaving like production infrastructure.
---
# What is SuperformOS?
Source: https://docs.superform.xyz/operate
SuperformOS is the operating system for programmable vaults. It lets operators launch, operate, and distribute SuperVaults with transparent rules, bounded execution, validator-attested pricing, and auditable operating history.
Most vaults call themselves non-custodial, but their strategy logic still lives in private workflows: manual rebalances, internal scripts, multisig decisions, or trusted operator processes. SuperformOS moves that logic into a visible operating surface.
Operators define what a vault is allowed to do. Users and partners can inspect the mandate, eligible actions, yield sources, pricing controls, and operating history. Execution stays inside the rules the vault has disclosed.
## What SuperformOS Does
Deploy a SuperVault, attach listing metadata, fund upkeep, and move from onchain creation to live distribution.
Configure approved actions, generate merkle roots, sync authorization onchain, and publish proof artifacts.
Build rule-based deposit, withdrawal, and rebalance logic with OMS-backed intent execution.
Pause vaults, pause services, and arm per-yield-source emergency liquidity exits when conditions change.
## Operator Jobs
Review [Prerequisites](/operate/prerequisites), [Authentication](/operate/authentication), and [Permissions](/operate/permissions).
Use the four-step [Create Vault](/operate/ui/create-vault) wizard to configure identity, operators, fees, and listing metadata.
Work through the [Setup Checklist](/operate/ui/setup-checklist): created, upkeep funded, hooks configured, yield sources whitelisted, strategy created, and catalog listed/available.
Monitor [Dashboard](/operate/ui/dashboard), [Strategy Canvas](/operate/ui/strategy), [Simulation](/operate/ui/simulation), [Intent History](/operate/ui/intent-history), [Yield Sources](/operate/ui/yield-sources), and [Merkle Trees](/operate/ui/merkle-trees).
Use [Distribute](/operate/ui/distribute) to review vault listings and provider actions, then check the public read-only route from [Dashboard](/operate/ui/dashboard) to confirm what allocators can inspect without manager access.
Use [Pause Operations](/operate/ui/pause), [Vault Settings alert delivery](/operate/ui/vault-settings#alert-delivery), audit logs, and notification channels to halt or drain risk when needed.
## API Surface
SuperformOS is backed by three main services:
- [Erebor](/operate/api/erebor) — vault management, settings, registries, merkle workflows, upkeep, users, notifications, and pause controls.
- [Strategy Engine](/operate/api/strategy) — strategies, ordering, live shards, intent projections, fills, and emergency locks.
- [OMS](/operate/api/oms) — DeFiX intent execution, events, hook transactions, and session-key readiness.
See [Operate API Overview](/operate/api/overview) for the service map.
SuperformOS prepares transactions and displays operational state. Protocol state changes still happen through signed onchain transactions from the authorized operator wallet.
---
# SuperformOS
Source: https://docs.superform.xyz/operate/concepts
SuperformOS is the operating layer for programmable vaults. It connects a vault's onchain contracts, operator-defined rules, automation services, proof artifacts, and price attestations into one verifiable operating surface.
The goal is simple: a vault should not be a black box. Users and partners should be able to inspect what a vault is allowed to do, how strategy decisions are constrained, how execution happens, and what changed over time.
## Built On ERC-7540
SuperformOS is not an offchain vault wrapper. It is an operating system around onchain [ERC-7540](https://eips.ethereum.org/EIPS/eip-7540) SuperVaults.
The vault contract holds depositor assets, issues shares, and enforces the core request and fulfillment flow. SuperformOS sits around that onchain vault: it helps operators define strategy rules, authorize executable actions, coordinate automation, maintain price attestations, and inspect operating history.
SuperformOS is an operating layer around onchain vault contracts. It does not replace those contracts as the source of custody or protocol enforcement.
## The Operating Stack
SuperformOS coordinates five layers:
| Layer | What it does | Where to go deeper |
|-------|--------------|--------------------|
| SuperVault contracts | Hold assets, issue shares, enforce vault-level constraints, and process ERC-7540 request flows | [SuperVaults](/learn/supervaults) |
| Operator configuration | Defines yield sources, oracles, managers, fees, service controls, and operational permissions | [Vault Settings](/operate/ui/vault-settings) |
| Strategy rules | Expresses deposit, withdrawal, rebalance, and claim logic as rule-based strategies | [Strategy Canvas](/operate/ui/strategy) |
| Merkle authorization | Converts approved actions into onchain roots and proof artifacts so execution stays inside disclosed permissions | [Merkle Trees](/operate/ui/merkle-trees) |
| Automation and intents | Evaluates rules, submits DeFiX intents, executes hooks, and records lifecycle evidence | [Intent History](/operate/ui/intent-history) |
## What Operators Define
Operators use SuperformOS to turn a vault mandate into explicit operating rules:
- which yield sources the vault can use
- which oracles price those positions
- which hooks and action parameters are authorized
- how deposits, withdrawals, claims, and rebalances are handled
- which operators have access
- when services should pause, resume, or drain risk
- how the vault appears in Superform distribution surfaces
Those choices are not just UI settings. The parts that change protocol behavior are prepared as calldata, signed by the authorized wallet, and enforced by the relevant onchain contracts.
SuperformOS never takes custody of vault assets or private keys. It prepares transactions, displays operational state, and coordinates automation. Onchain state changes still require authorized signatures.
## What The Protocol Enforces
SuperformOS is useful because the operating layer maps to protocol-enforced constraints:
| Constraint | Enforcement model |
|------------|-------------------|
| Custody | Depositor funds stay in the SuperVault contract. |
| Execution permissions | Hooks must match authorized merkle leaves before execution. |
| Strategy changes | Hook-root updates follow proposal and timelock flows. |
| Pricing | PPS updates require validator-signed attestations and staleness checks. |
| Fees | Fee bounds and fee accrual are enforced by SuperVault contracts and SuperGovernor constraints. |
| Emergency response | Operators can pause services, pause vault operations, or arm target-specific emergency exits. |
This is the difference between a private operating process and a verifiable strategy engine: the rules are visible, and execution is constrained by the system.
## How Execution Flows
The operator creates or imports a SuperVault, assigns operators, configures fees, funds upkeep, and prepares the vault for listing.
The operator configures allowed hooks and parameters. SuperformOS generates merkle roots and proof artifacts, then the authorized wallet proposes and publishes them onchain.
The operator defines rule-based strategies for deposits, withdrawals, claims, swaps, bridges, or rebalances.
Strategy Engine evaluates rules on ticks. When conditions pass, it submits DeFiX intents to OMS, which validates authorization and executes the corresponding hook transactions.
SuperformOS records strategy state, intent lifecycle, events, fills, errors, and transaction hashes so operators can inspect what happened and why.
## Price-Per-Share And Upkeep
PPS is the accounting value of one vault share in the underlying asset. It determines deposit and redemption exchange rates, high-water-mark fee accrual, and whether a vault is operational.
Validators compute PPS offchain, sign EIP-712 attestations, and submit updates onchain. Updates are checked for quorum, signature validity, timestamp staleness, replay protection, and abnormal movement.
Operators fund upkeep so validators can keep PPS current. If upkeep is insufficient or PPS goes stale, vault operations can be blocked until the issue is resolved.
## Roles In SuperformOS
SuperformOS uses operator language in the product, but some onchain roles have precise names:
| Role | Meaning |
|------|---------|
| Primary manager | Highest-authority onchain vault operator. Set at creation. |
| Secondary manager | Onchain delegate for operational work. |
| View-only user | Offchain read access for monitoring and review. |
| Registry maintainer | Admin role for global registry and provider workflows. |
| Validator | Attests vault PPS updates. |
| Guardian / governance | Handles protocol-level emergency and governance powers where applicable. |
Use [Permissions](/operate/permissions) for the full access matrix.
## Why This Matters
Programmable vaults need more than contracts and dashboards. They need an operating layer where strategy can be expressed, constrained, executed, reviewed, and distributed.
SuperformOS makes that operating layer inspectable:
- operators can launch vaults without rebuilding the full stack
- strategies can be described as rules instead of private process
- actions can be bounded by merkle authorization
- pricing can be backed by validator attestations
- operating history can be reviewed after the fact
That is the standard SuperformOS is built around: not just non-custodial vaults, but verifiable vault operations.
---
# Prerequisites and Access
Source: https://docs.superform.xyz/operate/prerequisites
## Wallet Requirements
SuperVaults uses [Dynamic.xyz](https://dynamic.xyz) for wallet connection and JWT issuance.
- **EVM-compatible wallet** (MetaMask, Rabby, Frame, or any WalletConnect-compatible wallet)
- **Native gas token** for each chain where you operate vaults
- **Operator access** assigned on at least one vault before SuperformOS features are accessible
View-only users are added offchain and do not require a wallet connection. All write operations require at minimum a secondary manager role.
## Supported Chains
| Chain | Chain ID | Status |
|-------|----------|--------|
| Ethereum Mainnet | 1 | ✅ Live |
| Base | 8453 | ✅ Live |
| HyperEVM | 999 | ✅ Live |
| Flare | 14 | ✅ Live |
The SuperVault contract architecture supports additional chains. Contact the Superform team for expansion requests.
## SuperformOS
| Environment | URL |
|-------------|-----|
| Production | [os.superform.xyz](https://os.superform.xyz) |
## API Base URLs
Each backend service exposes its own base URL. Use these when building automation:
| Service | Base URL | OpenAPI Spec |
|---------|----------|-------------|
| **Erebor** (management API) | `https://erebor.superform.xyz` | ✅ Available |
| **Strategy Engine** | `https://strategy.superform.xyz` | ✅ Available |
| **OMS** (DeFiX intents) | `https://oms.superform.xyz` | ✅ Available |
## Role Requirements by Page
| Page | Minimum Role |
|------|-------------|
| Home | Public (no auth) |
| Dashboard | Operator |
| Strategy | Operator |
| Yield Sources | Operator |
| Merkle Trees | Operator |
| User Management | Primary Manager |
| Vault Settings | Primary Manager |
| Pause | Operator |
| Create Vault | Operator (you become primary manager of the new vault) |
| Alerts | Operator |
## Getting Access
**Joining an existing vault:** The primary manager adds you via [User Management](/operate/ui/user-management) as a secondary manager (onchain transaction) or view-only user (offchain).
**Creating a new vault:** Any wallet with operator access can create a vault and automatically becomes its primary manager.
JWT auth flow, token storage, and programmatic access.
---
# Authentication
Source: https://docs.superform.xyz/operate/authentication
## Auth Flow
SuperVaults uses [Dynamic.xyz](https://dynamic.xyz) for wallet connection and JWT issuance.
Open [os.superform.xyz](https://os.superform.xyz) and click **Connect Wallet**. Dynamic.xyz presents a wallet selection modal supporting MetaMask, Rabby, WalletConnect, and others.
Your wallet signs a non-transactional message. Dynamic.xyz verifies the signature and issues a JWT tied to your wallet address.
SuperformOS stores auth state in `localStorage`:
- `curator_jwt` — the Bearer token used on all API requests
- `curator_address` — connected wallet address
- `curator_is_manager` — boolean flag
- `curator_vaults` — array of vault addresses you operate
The app calls `GET /api/v1/auth/me` on load to fetch your vault roles and confirm the JWT is valid.
## Primary Auth Endpoint
```bash
GET /api/v1/auth/me
Authorization: Bearer
```
Returns the current user with all assigned vault roles. Use this endpoint when building integrations.
Response:
```json
{
"wallet_address": "0xabc...",
"is_manager": true,
"vaults": [
{
"vault_address": "0xdef...",
"chain_id": 8453,
"role": "primary_manager"
}
]
}
```
`GET /api/v1/auth/me` returns the current user with all assigned vault roles. Use this endpoint when building integrations.
## JWT Verification Endpoint
```bash
GET /api/v1/auth/verify
Authorization: Bearer
```
Returns:
```json
{
"verified": true,
"walletAddress": "0xabc...",
"isManager": true,
"vaults": ["0xdef..."]
}
```
## Token Lifecycle
- JWTs expire after a session period defined by Dynamic.xyz
- Any `401 Unauthorized` from Erebor triggers automatic logout via `authService.handleAutoLogout()`
- Reconnecting your wallet re-issues a fresh JWT
## Public Endpoints (No Auth)
| Endpoint | Description |
|----------|-------------|
| `GET /api/v1/public/vaults` | All SuperVault deployments with TVL and APY |
| `GET /api/v1/registry/token-assets` | Token asset metadata |
## Programmatic Authentication
For automation scripts, generate a JWT without the browser:
1. Sign an EIP-712 auth message with your wallet's private key
2. Submit the signature to Dynamic.xyz to receive a JWT
3. Pass the JWT as `Authorization: Bearer ` on all requests
```python Python
import requests
EREBOR_URL = "https://erebor.superform.xyz"
JWT = ""
headers = {"Authorization": f"Bearer {JWT}"}
resp = requests.get(f"{EREBOR_URL}/api/v1/auth/me", headers=headers)
resp.raise_for_status()
user = resp.json()
print(f"Connected as: {user['wallet_address']}")
print(f"Manages {len(user['vaults'])} vault(s)")
```
```typescript TypeScript
const EREBOR_URL = "https://erebor.superform.xyz";
const JWT = "";
const headers = { Authorization: `Bearer ${JWT}` };
const resp = await fetch(`${EREBOR_URL}/api/v1/auth/me`, { headers });
if (!resp.ok) throw new Error(`Auth failed: ${resp.status}`);
const user = await resp.json();
console.log(`Connected as: ${user.wallet_address}`);
console.log(`Manages ${user.vaults.length} vault(s)`);
```
Never hardcode JWTs in source code. Use environment variables (for example, `SUPERFORMOS_JWT`) and rotate tokens regularly. JWTs grant full manager-level access to your vaults.
---
# Permissions Model
Source: https://docs.superform.xyz/operate/permissions
The SuperVaults system separates protocol governance, vault accounting, and day-to-day operations. The safest mental model is:
- **Primary manager** is the highest-authority vault operator within protocol, timelock, Merkle, fee, PPS, and accounting constraints.
- **Secondary managers** execute delegated operational actions within the same onchain constraints.
- **View-only users** observe the vault.
- **Registry maintainers** maintain global metadata.
- **Session keys / keepers** execute narrowly-authorized automation.
## Role Tiers
| Role | Onchain? | Scope | Typical Use |
|------|----------|-------|-------------|
| Primary Manager | Yes | Highest-authority vault operations | Multisig / hardware-backed operator |
| Secondary Manager | Yes | Delegated operational execution | PM, operations, risk |
| View Only | No | Read access | Analysts, auditors, monitors |
| Registry Maintainer | No | Global registry CRUD | Internal registry operations |
| Session Key / Keeper | Yes, delegated | Specific executable actions | Strategy and OMS automation |
## Primary Manager
The primary manager is set at vault creation and is the highest-authority vault operator. This role does not take custody of depositor assets: assets remain in vault contracts and manager actions are constrained by protocol logic, active Merkle roots, PPS/accounting checks, fee limits, and timelocks for sensitive changes.
Primary manager responsibilities:
- Vault settings and sensitive configuration.
- Secondary-manager assignment.
- Session-key grant and revoke flows.
- Upkeep funding and withdrawal control.
- Pause, unpause, and emergency operations.
- Merkle root proposal / publish workflows.
Treat the primary manager as production infrastructure. Use a multisig or hardware-backed wallet. If the primary manager key is compromised, the attacker may be able to exercise the role's authorized operating powers, subject to protocol checks, timelocks, Merkle allowlists, and accounting constraints.
## Secondary Managers
Secondary managers are onchain delegates for operational work. They can execute only the actions authorized for their role and, for hook execution, only pre-authorized hook/parameter combinations proven against the active Merkle roots.
They can typically:
- Manage strategies.
- Configure yield sources.
- Work with merkle tree configuration.
- Pause/unpause operational services.
- Add view-only collaborators.
They cannot unilaterally bypass primary-only settings, timelocks, Merkle validation, PPS checks, fee limits, or vault accounting.
## What Managers Cannot Do
Manager roles are operational roles, not custody of depositor wallets or unrestricted control over vault assets. Managers cannot:
- Withdraw depositor assets to arbitrary wallets outside vault redemption, strategy, or emergency flows enforced by the contracts.
- Execute arbitrary calldata against yield sources; hook execution must match pre-authorized Merkle leaves and protocol validation.
- Bypass ERC-7540 redemption accounting, PPS-based calculations, slippage expectations, or share/accounting updates when fulfilling redemptions.
- Instantly apply sensitive changes that are timelocked and visible onchain, such as fee updates, certain PPS parameter changes, upkeep withdrawals, primary-manager changes, and hook-root updates.
- Override global protocol constraints such as banned leaves, pause state, validator-secured PPS requirements, or governance-maintained limits.
## View-Only Users
View-only access is offchain. It grants dashboard visibility without transaction authority.
Use it for:
- Risk monitors.
- Analysts.
- Auditors.
- Partners who need vault state without write access.
## Registry Maintainers
Registry maintainers manage global metadata shared across vaults, including:
- Yield source registry entries.
- Oracle registry entries.
- Hook registry entries.
- Token asset metadata.
This role is not vault-scoped and should be tightly controlled.
## Session Keys and Keepers
Session keys are delegated execution keys used by automation. A session key does not imply broad manager authority; it authorizes a specific automation path.
Keepers and OMS execution still depend on:
- Active upkeep funding.
- Valid session key readiness.
- Active merkle root and proofs.
- Non-paused vault / services.
- Strategy state and emergency locks.
If any of those gates fail, automation should stop even when a session key exists.
## Permission Matrix
| Action | Primary | Secondary | View Only | Registry Maintainer | Session Key / Keeper |
|--------|---------|-----------|-----------|---------------------|----------------------|
| View dashboard | ✅ | ✅ | ✅ | — | — |
| Create vault | ✅ | — | — | ✅ | — |
| Fund upkeep | ✅ | ✅ | ❌ | — | — |
| Withdraw upkeep | ✅ | Usually ❌ | ❌ | — | — |
| Configure strategies | ✅ | ✅ | ❌ | — | — |
| Reorder strategies | ✅ | ✅ | ❌ | — | — |
| Manage yield sources | ✅ | ✅ | ❌ | — | — |
| Configure hooks / merkle roots | ✅ | ✅ | ❌ | — | — |
| Propose or publish merkle root | ✅ | Permissioned | ❌ | — | — |
| Grant / revoke session key | ✅ | Usually ❌ | ❌ | — | — |
| Pause vault | ✅ | ✅ | ❌ | — | — |
| Arm emergency exit | ✅ | ✅ | ❌ | — | — |
| Add/remove secondary managers | ✅ | ❌ | ❌ | — | — |
| Add/remove view-only users | ✅ | ✅ | ❌ | — | — |
| Registry CRUD | — | — | — | ✅ | — |
| Execute authorized hooks | — | — | — | — | ✅ |
## API Patterns
```bash
# List vault users
GET /api/v1/users?vault_address={address}&chain_id={chainId}
# Prepare add secondary manager transaction
POST /api/v1/users/secondary-manager/prepare
# Prepare remove secondary manager transaction
POST /api/v1/users/secondary-manager/remove/prepare
# Add view-only user offchain
POST /api/v1/users/view-only
# Remove view-only user offchain
DELETE /api/v1/users/view-only/{wallet}
```
A `403 Forbidden` means the connected wallet lacks the required role for that operation.
---
# Quickstart
Source: https://docs.superform.xyz/operate/quickstart
This guide follows the operator path from vault deployment to a live Superform listing. Use it for sequence; use [Setup Checklist](/operate/ui/setup-checklist) as the canonical readiness source after the vault exists.
Prerequisites: operator access, connected EVM wallet, gas on the deployment chain, provider profile, and the token/yield-source details for the vault.
Open **Create Vault** (`/vaults/create`). The editable wizard has four steps:
1. **Basic Information** — select chain, provider, underlying asset, listing metadata, and the computed onchain name/symbol.
2. **Managers** — assign the primary manager and optional secondary managers.
3. **Fees** — configure fee recipient, performance fee, management fee, PPS timing, and redemption timing.
4. **Review** — inspect the full configuration before the transaction phase.
After review, the modal moves through transaction signing, indexing, optional yield-source setup, and completion. See [Create Vault](/operate/ui/create-vault).
Open **Setup**. Treat it as the canonical go-live checklist. It coordinates the required rows for creation, upkeep, hook authorization, yield sources, strategy coverage, and Superform availability.
Do not maintain a second checklist from this page. If the Setup page disagrees with another surface, Setup wins. See [Setup Checklist](/operate/ui/setup-checklist).
Open **Vault Settings → Upkeep**. Fund the required upkeep balance. Erebor marks the vault active and discoverable once the funding gate is satisfied.
Without upkeep, operator pages can still show the vault by direct link, but keeper automation and public discoverability remain gated. Ethereum L1 vaults currently require at least `100,000 $UP`; non-L1 vaults require at least `1,000 $UP`.
Open **Yield Sources**. Add each target yield source with its paired oracle. Bulk add, bulk remove, and bulk oracle update flows prepare a single transaction for multiple rows.
Emergency-exit readiness is tied to the whitelist: each whitelisted yield source can have a paired `EMERGENCY_EXIT` strategy used by the Pause page to drain that source during an incident.
Open **Merkle Trees** and follow the manager-root flow:
1. Draft a root config.
2. Activate the config.
3. Generate the merkle tree.
4. Have the primary manager propose and publish the root onchain.
Keepers and OMS intents need active merkle proofs for each hook/parameter combination they execute.
Open **Strategy Canvas**. Create user-defined strategies for deposits, withdrawals, and rebalances. Strategy priority is server-managed: use drag-to-reorder, which persists through `POST /api/v1/strategies/reorder`. Create and update requests do not carry `priority`.
Before a strategy can publish intents, the OMS session key must be ready and the active merkle tree must contain the required withdrawal/deposit hooks.
Open **Distribute**. Confirm the vault has the metadata, visibility, and availability needed for Superform surfaces.
Onchain created means the vault contract exists. Available in Superform means the vault is indexed, activated, configured, and visible to allocator-facing surfaces. See [Distribute](/operate/ui/distribute).
Use **Dashboard** for vault health, **Intent History** for execution traces, and **Pause** for incident controls. Intent History maps Strategy Engine rows to OMS events, fills, gas usage, mined timestamps, and hook transaction details.
## Source of Truth
After creation, [Setup Checklist](/operate/ui/setup-checklist) is the source of truth for go-live readiness. This quickstart intentionally avoids duplicating its row logic.
Use the other pages for the work behind each row:
| Work | Page |
|------|------|
| Upkeep and activation | [Vault Settings](/operate/ui/vault-settings) |
| Hook authorization and proof publishing | [Merkle Trees](/operate/ui/merkle-trees) |
| Yield-source whitelist and oracles | [Yield Sources](/operate/ui/yield-sources) |
| Strategy authoring and ordering | [Strategy Canvas](/operate/ui/strategy) |
| Distribution availability | [Distribute](/operate/ui/distribute) |
| Execution monitoring | [Intent History](/operate/ui/intent-history) |
---
# Home
Source: https://docs.superform.xyz/operate/ui/home

The **Home** page (`/`) is the top-level entry point at [os.superform.xyz](https://os.superform.xyz). It provides a global overview of all SuperVault deployments. No authentication required. Data refreshes from `GET /api/v1/public/vaults` (Erebor) with a 5-minute stale time.
## Overview Cards
Three summary cards appear at the top of the page:
| Card | Description |
|------|-------------|
| **Total TVL** | Aggregate USD value locked across all vaults |
| **Total Vaults** | Number of deployed SuperVaults |
| **Best APY** | Highest current annualized yield across all vaults |
## Vault List
Below the overview cards, a searchable and filterable vault list displays every deployed SuperVault:
| Field | Description |
|-------|-------------|
| Vault Name | Operator-defined name |
| Underlying Asset | Token depositors supply (USDC, WETH, etc.) |
| TVL (USD) | Total value locked |
| APY | Current annualized yield |
| Chain | Ethereum or Base |
### Filtering and Search
| Control | Options |
|---------|---------|
| Chain filter | All, Ethereum (1), Base (8453) |
| Search | Text match by vault name or asset |
Clicking a vault opens the public read-only vault route at `/supervaults/{chain_name}/{vaultAddress}`. That route uses the same allocator-facing dashboard context described in [Dashboard](/operate/ui/dashboard), while manager-only actions remain in Operate.
## Navigation Context
Home sits alongside the operator and distribution sections in SuperformOS:
| Section | Path | Purpose |
|---------|------|---------|
| **Home** | `/` | Global overview — TVL, vault count, APY, vault list |
| **Operate** | `/operate` | Per-vault operation - dashboard, setup, strategy, simulation, merkle trees, yield sources, and settings |
| **Distribute** | `/distribute` | Vault and provider distribution workflows |
## API Reference
| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| `/api/v1/public/vaults` | GET | None | All vault deployments with TVL/APY |
| `/api/v1/registry/token-assets` | GET | None | Token metadata (symbol, decimals, logo) |
```json
// GET /api/v1/public/vaults — response shape
[
{
"vault_address": "0x...",
"escrow_address": "0x...",
"chain_id": 8453,
"name": "USDC Yield Vault",
"symbol": "svUSDC",
"vault_share_decimals": 18,
"underlying_asset_symbol": "USDC",
"underlying_asset_decimals": 6,
"underlying_asset_address": "0x...",
"tvl_usd": 1250000.00,
"apy": 0.0847
}
]
```
Token metadata is fetched separately via `GET /api/v1/registry/token-assets?chain_id={}&addresses={}` and cached for the session (infinite stale time).
---
# Create Vault
Source: https://docs.superform.xyz/operate/ui/create-vault
The Create Vault flow (`/vaults/create`) deploys a new SuperVault and captures the metadata needed to present it in Superform distribution surfaces. Creation is intentionally split into two phases:

1. Editable configuration before anything is signed.
2. Operational setup after the vault exists onchain.
## Before You Start
You need:
- A connected wallet with permission to create and manage the vault.
- The underlying token available in the token registry, or enough metadata to register it.
- An approved provider record for the vault's listing metadata.
- The primary manager wallet that will administer vault-level settings within protocol constraints.
- Fee recipient and fee parameters.
## Editable Wizard Steps
The wizard has four editable steps. You can move back through these steps until you enter the transaction phase.
### 1. Basic Information
Basic Information combines the old chain, asset, identity, and listing screens into one step.
Configure:
- **Chain** — the deployment network. This is permanent after deployment.
- **Provider** — the approved managed provider associated with the vault.
- **Underlying asset** — the token users deposit into the vault. You can select a registered token or register a custom token.
- **Distribution metadata** — friendly name, description, category, and provider IDs used by Superform distribution surfaces.
- **Onchain identity** — name and symbol. By default these are computed from provider + asset, with an optional manual override.
The onchain vault name and symbol are protocol identity. The friendly name, category, provider IDs, and description are distribution metadata used by Superform surfaces.
### 2. Managers
Set the primary manager and optional secondary managers.
- The **primary manager** administers vault settings, user management, session keys, and emergency authority within protocol constraints.
- **Secondary managers** can execute delegated vault operations but cannot bypass primary-only settings, Merkle validation, timelocks, or vault accounting.
For production vaults, use a hardware-backed wallet or multisig as the primary manager.
### 3. Fees
Configure:
- Performance fee.
- Management fee.
- Fee recipient.
- PPS timing parameters.
- Optional redeem timelock.
Fee bounds and protocol constraints are read from protocol configuration. The dashboard prepares valid calldata; your wallet signs the resulting transaction.
### 4. Review
Review everything before entering the transaction phase:
- Chain and underlying asset.
- Provider and distribution metadata.
- Onchain name and symbol.
- Managers.
- Fees and timing parameters.
After this point, the wizard moves into signing, indexing, activation, and post-create setup.
## Transaction, Indexing, and Activation
### Sign the Creation Transaction
The dashboard calls Erebor to prepare calldata:
```bash
POST /api/v1/vaults/create/prepare
```
Your wallet signs and submits the transaction onchain. Erebor does not execute the transaction for you.
### Confirm and Index
After the transaction is broadcast, the dashboard confirms the hash:
```bash
POST /api/v1/vaults/create/confirm
```
Erebor creates an indexing job and the UI polls:
```bash
GET /api/v1/vaults/create/jobs/{job_id}
```
Statuses are `pending`, `running`, `completed`, or `failed`.
During indexing, the dashboard also submits distribution metadata when the vault address is resolved. If submission fails, the vault can still exist onchain; confirm availability from the Distribute surface.
`409 Conflict` on confirm means a creation job already exists for that transaction hash. Check the existing job before retrying.
### Activate With Upkeep
After indexing, the wizard can send the operator to **Vault Settings -> Upkeep**. Upkeep funding is the activation gate for automation and discoverability. The setup route can deep-link with `panel=upkeep&activateVault=1` so the manager lands directly on the funding action.
### Set Up Yield Sources
Creation can continue into yield-source setup. Add at least one whitelisted source and paired oracle before creating strategies or routing deposits.
### Complete or Finish Later
The vault can exist before all setup rows are complete. If you leave the wizard after deployment, use [Setup Checklist](/operate/ui/setup-checklist) to resume activation, hooks, yield sources, strategy, and distribution work.
## Post-Create Setup
Once the vault is indexed, complete the launch checklist:
1. Vault exists onchain and appears in dashboard data.
2. Upkeep is funded so the vault becomes active and discoverable.
3. Hooks are configured in the active merkle root config.
4. Yield sources are whitelisted and paired with oracles.
5. At least one strategy exists for the vault.
6. The vault is visible and available in Superform distribution surfaces.
See [Setup Checklist](/operate/ui/setup-checklist) for the canonical readiness flow.
## API Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/registry/token-assets` | GET | Browse registered token assets |
| `/api/v1/registry/token-assets` | PUT | Register or upsert a custom token |
| `/api/v1/vaults/create/prepare` | POST | Prepare creation calldata |
| `/api/v1/vaults/create/confirm` | POST | Confirm creation transaction hash |
| `/api/v1/vaults/create/jobs/{id}` | GET | Poll indexing job status |
### PrepareCreateVaultRequest
```json
{
"chain_id": 8453,
"asset": "0xUSDC...",
"name": "Provider USDC SuperVault",
"symbol": "proUSDC",
"main_manager": "0xPrimaryManager...",
"secondary_managers": [],
"min_update_interval": 3600,
"max_staleness": 86400,
"fee_config": {
"performance_fee_bps": 1000,
"management_fee_bps": 200,
"recipient": "0xFeeRecipient..."
}
}
```
### ConfirmCreateVaultRequest
```json
{
"tx_hash": "0xabc...",
"chain_id": 8453,
"vault_config": {
"redeem_timelock_seconds": 0
}
}
```
---
# Setup Checklist
Source: https://docs.superform.xyz/operate/ui/setup-checklist
The Setup page turns a newly-created SuperVault into an operational and discoverable vault. Use it after Create Vault and after any major configuration reset.

Use this page to move from created to available: fund upkeep, configure hooks, add yield sources, create strategies, and confirm Superform availability.
If no vault is selected, the page asks the operator to select one from the Operate sidebar. The checklist is vault-scoped: every row is calculated from the selected chain and vault address.
## Checklist rows
| Row | Complete when | Primary action |
|-----|---------------|----------------|
| **Vault is created** | Vault exists onchain and is readable in dashboard data. | Create Vault |
| **Upkeep is staked** | Vault is active and can run operational automation. | Fund Upkeep |
| **Hooks are set up** | At least one hook is configured in the active root config. | Configure Hooks |
| **Yield sources are set up** | At least one yield source is whitelisted for this vault. | Configure Yield Sources |
| **Strategy is set up** | At least one strategy exists for this vault. | Open Strategy |
| **Vault is live on Superform** | Vault is visible and available in Superform distribution surfaces. | Open Distribute |
Statuses are `complete`, `in_progress`, `needs_setup`, or `blocked`.
## Activation gate
Upkeep is the activation gate. Fund upkeep from **Vault Settings → Upkeep** to enable automation and discovery. The activation route can deep-link with `panel=upkeep&activateVault=1` so the primary manager lands on the funding action.
Current Erebor-facing activation fields used by the UI include:
| Field | Meaning |
|-------|---------|
| `activation.is_active` | Whether the vault is eligible for keeper operations. |
| `activation.is_discoverable` | Whether the vault is surfaced in discovery. |
| `activation.status` | `active` or `insufficient_upkeep`. |
| `activation.reason` | Optional explanation when the vault is not active. |
| `upkeep_info.balance` | Current upkeep balance, in raw token units. |
| `upkeep_info.threshold.required_balance` | Required balance for activation, in raw token units. |
| `upkeep_info.threshold.shortfall` | Amount still needed to clear the gate. `0` means the threshold is satisfied. |
Current minimum upkeep thresholds:
| Vault Chain | Minimum Upkeep for Activation |
|-------------|-------------------------------|
| Ethereum L1 | `100,000 $UP` |
| Non-L1 chains | `1,000 $UP` |
## Distribution Evidence
The live-on-Superform row compares the managed vault against distribution data. Evidence includes `visibility` and `availability`, for example `listed` and `available`. If no matching distribution entry exists, the vault is onchain and manager-visible but not available in Superform allocator-facing surfaces.
## Troubleshooting
| Symptom | Likely cause | Next step |
|---------|--------------|-----------|
| Vault readable but not active | Upkeep shortfall. | Fund upkeep. |
| Hooks count is zero | No active root config. | Draft, activate, generate, and propose a merkle root. |
| Yield sources count is zero | Nothing is whitelisted. | Add yield sources and oracles. |
| Strategy count is zero | No strategy exists. | Create a strategy after adding sources. |
| Not live on Superform | Missing or unavailable distribution entry. | Open Distribute and confirm visibility/availability. |
## Operator rule
Route deposits only after the checklist shows that upkeep, hooks, yield sources, strategies, and distribution availability are all complete.
---
# Dashboard
Source: https://docs.superform.xyz/operate/ui/dashboard

The Dashboard is the primary monitoring view for a single vault. It appears in the manager Operate surface and as the read-only public route at `/supervaults/{chain_name}/{vaultAddress}`. Nine sidebar sections cover every dimension of vault health: financial state, allocation composition, pricing, fee accrual, access control, performance history, risk metrics, depositor analytics, and operational throughput.
In Operate, select your chain and vault from the app controls. Public detail links use `/supervaults/{chain_name}/{vaultAddress}`. Financial data refreshes every 15 seconds from `GET /api/v1/vaults/{chain_id}/{vault_address}` plus Erebor analytics sub-endpoints.
## Public Read-Only Route
Allocator-facing vault pages use the same dashboard data model without exposing manager controls. Use the public route to inspect vault state, tabs, settings context, strategy visibility, and merkle proof status exactly as a non-manager would see them.
Manager-only actions such as settings updates, user changes, merkle generation, strategy editing, upkeep deposits, and pause flows stay in authenticated Operate pages.
## 1. Overview
Top-level vault health metrics:
| Metric | Description |
|--------|-------------|
| TVL (USD) | Current total value locked |
| Current PPS | Last onchain recorded price-per-share |
| Calculated PPS | Off-chain computed PPS (may differ during update intervals) |
| HWM PPS | All-time high-water mark. Performance fees accrue only above this level. |
| Total Supply | Outstanding vault shares |
| Free Assets | Assets in vault not deployed to yield sources |
| Escrowed Assets | Assets pending cross-chain redemptions |
| Pause Status | Whether vault strategy execution is paused |
| $UP Upkeep | Upkeep token balance and payment status |
| Staleness Countdown | Time until PPS expires. Client-calculated from `last_pps_update_timestamp`. |
## 2. Allocation
Pie chart showing current distribution of vault assets across yield sources. Each slice represents one whitelisted yield source with its allocation amount and percentage of total.
## 3. PPS & Pricing
PPS history chart (time series from `analytics/pps`). Current, calculated, and HWM values displayed alongside staleness countdown with visual indicator. Min/max update intervals from SuperGovernor config.
## 4. Fees & Upkeep
| Field | Description |
|-------|-------------|
| Management Fee | Annualized bps on TVL |
| Performance Fee | Bps on yield above HWM |
| Pending Performance Fee | Total accrued, Superform share, recipient share, profit since HWM |
| Can Skim | Whether pending fees are claimable now |
Fee configuration data comes from the vault detail endpoint (`GET /api/v1/vaults/{chain_id}/{address}`) in the `fee_config` field. Fee history is derived from vault analytics and audit log data.
## 5. Managers
Lists all wallet addresses with roles on this vault: primary manager, secondary managers, view-only users.
## 6. Performance
- APY overview: current, 7-day, 30-day from `analytics/overview`
- APY time series chart with selectable periods
- TVL time series chart with selectable periods
- 24h and 7d TVL change
## 7. Risk
| Metric | Description |
|--------|-------------|
| Risk Score | Composite 0-100 score |
| Risk Level | Low / Medium / High classification |
| Concentration Score | Exposure to any single yield source |
| Max Drawdown (30d) | Worst peak-to-trough PPS decline |
| Sharpe Ratio | Risk-adjusted return |
| Volatility (Daily) | Daily PPS standard deviation |
| Top Holder % | Share of TVL held by the largest depositor |
Risk data has a 2-minute stale time.
## 8. Users
From `analytics/users`: total users, active users (24h/7d/30d), new users (24h/7d/30d), churned users, retention rate (7d/30d), average deposit size. Includes retention cohort table and top holders table.
## 9. Operations
From `analytics/operations`: transaction count, deposit and withdrawal counts, gas costs (average and total), slippage (average and max), net flow. Deposit/withdraw volume chart and transaction history table.
## Zap Terminal
The Zap Terminal opens as a modal overlay providing AI-powered vault analysis: a narrative covering performance, risk, yield sources, fees, and liquidity. Also includes an operator-focused social feed and WebSocket-based AI chat for vault Q&A.
The Zap Terminal analysis endpoint (`GET /api/v1/vaults/{vaultAddress}/ai/analysis?chain_id={}`) is operational and used by the Zap Terminal.
## API Endpoints
| Endpoint | Stale Time |
|----------|-----------|
| `GET /api/v1/vaults/{chain_id}/{address}` | 15s |
| `GET .../analytics/overview` | 15s |
| `GET .../analytics/pps` | 15s |
| `GET .../analytics/performance` | 15s |
| `GET .../analytics/performance/apy?period={p}` | 15s |
| `GET .../analytics/performance/tvl?period={p}` | 15s |
| `GET .../analytics/risk` | 2min |
| `GET .../analytics/risk/concentration` | 2min |
| `GET .../analytics/risk/drawdown` | 2min |
| `GET .../analytics/risk/volatility` | 2min |
| `GET .../analytics/users` | 30s |
| `GET .../analytics/users/retention` | 30s |
| `GET .../analytics/users/top` | 30s |
| `GET .../analytics/users/cohorts` | 30s |
| `GET .../analytics/operations` | 30s |
| `GET .../analytics/operations/transactions` | 30s |
| `GET .../analytics/operations/volume` | 30s |
| `GET .../analytics/social` | 60s |
| `GET .../analytics/export` | on-demand |
---
# Strategy Canvas
Source: https://docs.superform.xyz/operate/ui/strategy

New to strategy authoring? Start with [Strategy Quickstart](/operate/strategy/quickstart), then use [Strategy DSL](/operate/strategy/dsl) and [Action Config](/operate/strategy/action-config) as field references.
The Strategy Canvas is where operators define automated vault behavior. Strategies evaluate rules on ticks and dispatch DeFiX intents to the OMS when conditions pass.
Public SuperVault detail pages expose the canvas as a read-only tab. Manager actions such as create, update, reorder, run, stop, and archive are available only in the authenticated Operate surface.
The canvas is organized by action lane:
| Lane | Actions | Purpose |
|------|---------|---------|
| Inflow | `DEPOSIT` | Deploy idle assets into yield sources |
| Outflow | `WITHDRAWAL`, `CLAIM` | Source liquidity and claim proceeds |
| Rebalance | `REBALANCE`, `SWAP`, `BRIDGE` | Move capital between sources or routes |
## Strategy Lifecycle
| State | Meaning |
|-------|---------|
| `CREATED` | Drafted but not active |
| `IDLE` | Ready but not ticking |
| `RUNNING` | Evaluating rules and eligible to publish intents |
| `ERROR` | Runtime error; inspect logs/events |
| `ARCHIVED` | Soft-deleted |
The Strategy Engine API currently models these states directly. Vault-level kill switches and emergency locks can still prevent a strategy from publishing even when the stored state is `RUNNING`.
## Create or Update a Strategy
A strategy contains:
- Name.
- Indicators.
- Rule tree.
- Action config.
- Conviction config.
- Risk parameters.
- Cooldown and concurrency controls.
Create and update calls do **not** carry priority. Ordering is handled by the dedicated reorder endpoint.
Updates require a `version` for optimistic concurrency. A stale version returns `409 Conflict`.
## Authoring References
Strategy authoring is split into focused references:
| Page | Use it for |
|------|------------|
| [Strategy Quickstart](/operate/strategy/quickstart) | Build and POST a first deposit/withdrawal reserve manager. |
| [Strategy DSL](/operate/strategy/dsl) | Rule tree syntax, expressions, variables, and validation behavior. |
| [Action Config](/operate/strategy/action-config) | OMS dispatch fields and yield-source execution requirements. |
| [Strategy Troubleshooting](/operate/strategy/troubleshooting) | Diagnose strategies that do not tick, publish, or fill. |
| [Strategy Anti-Patterns](/operate/strategy/anti-patterns) | Avoid designs that pass validation but fail operationally. |
## Reorder Strategies
Drag within a lane to change priority. The UI persists order through:
```bash
POST /api/v1/strategies/reorder
```
The request includes `vault_id`, `lane`, and the complete ordered list of non-archived strategy IDs for that lane. The backend atomically assigns priorities `1..N` from list position.
The ordered list must include every non-archived strategy in the lane exactly once. Priority is server-managed; create and update requests no longer set it.
## Rule Trees
Rules are boolean trees:
| Node | Meaning |
|------|---------|
| `EXPR` | Evaluate one expression |
| `AND` | All children pass |
| `OR` | At least one child passes |
| `NOT` | Negate one child |
| `VOTE` | N-of-M children pass |
Conviction config can require repeated passing ticks before publishing an intent.
## Action Config
Action config tells the engine what to do after rules pass. Common fields include:
- `action`
- `size_expr`
- `objective`
- `execution_name`
- `execution_address`
- `target_address`
- `from_address`
- `target_type`
- `execution_params`
- `max_slippage_bps`
A target must be whitelisted and authorized by the active merkle root before OMS execution can succeed.
## Emergency Locks
Emergency liquidity exits create target-scoped locks. While a lock exists for a yield source address:
- User-defined strategies targeting that address cannot publish new intents.
- Affected toggles and reorder handles are disabled in the UI.
- Strategies on other targets remain operable.
- The paired `EMERGENCY_EXIT` strategy is controlled from [Pause Operations](/operate/ui/pause), not the normal editor.
The canvas reads `emergency_locks` from shard / vault state and displays a banner when active.
## Intent History
The Intent History view is the operator-facing execution ledger for strategies. It maps Strategy Engine projections to OMS events and fills.
Use it to answer:
- Which strategy created the intent?
- What lifecycle state is it in?
- Which OMS events have arrived?
- Were fills mined?
- What gas was used?
- Which transaction hash settled the fill?
Prefer the search endpoint for new tooling:
```bash
GET /api/v1/intents
GET /api/v1/intents/{intent_id}/events
GET /api/v1/intents/{intent_id}/fills
GET /api/v1/fills
```
See [Strategy Engine API](/operate/api/strategy) and [OMS API](/operate/api/oms).
## Session Key Gate
Strategies need OMS execution readiness. If a vault lacks a valid session key, the canvas blocks automation and points operators to grant or renew the key.
See [OMS API](/operate/api/oms#session-keys) for the session-key endpoints.
## API Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/strategies` | GET | List strategies with filters |
| `/api/v1/strategies` | POST | Create strategy |
| `/api/v1/strategies/{id}` | GET | Get strategy |
| `/api/v1/strategies/{id}` | PUT | Update strategy with version |
| `/api/v1/strategies/{id}` | DELETE | Archive strategy |
| `/api/v1/strategies/{id}/state` | PATCH | Transition lifecycle state |
| `/api/v1/strategies/reorder` | POST | Persist lane order |
| `/api/v1/intents` | GET | Search intent history |
| `/api/v1/intents/facets` | GET | Intent filter facets |
| `/api/v1/intents/{intent_id}/events` | GET | OMS event timeline |
| `/api/v1/intents/{intent_id}/fills` | GET | Fills for one intent |
| `/api/v1/fills` | GET | Search fills across intents |
| `/api/v1/engine/shards/{vault_id}/state` | GET | Live shard state and emergency locks |
---
# Strategy Quickstart
Source: https://docs.superform.xyz/operate/strategy/quickstart
This is the shortest path from no strategy to a validated deposit/withdrawal pair. You will build a reserve manager for one yield source: when idle assets are too high, it deposits; when idle assets are too low, it withdraws.
## What You Are Building
A drift-trigger reserve pair:
- A `DEPOSIT` strategy fires when `vault_free_assets` is above 10% of vault TVL.
- A `WITHDRAWAL` strategy fires when `vault_free_assets` is below 5% of vault TVL.
- The withdrawal half refills the reserve toward a 7.5% target.
Two strategies, one yield source, one `EXPR` rule each, no indicators.
## 1. Choose the Lane
Strategy actions group into lanes. The lane determines where the strategy appears in the canvas and how ordering works.
| Lane | Actions | This guide |
|------|---------|------------|
| Inflow | `DEPOSIT` | Deposit half |
| Outflow | `WITHDRAWAL`, `CLAIM` | Withdrawal half |
| Rebalance | `REBALANCE`, `SWAP`, `BRIDGE` | Not used |
See [Strategy Canvas](/operate/ui/strategy) for the UI model.
## 2. Pick the Action
Each strategy has one `action_config.action`. Use `DEPOSIT` for the inflow strategy and `WITHDRAWAL` for the outflow strategy.
The other `action_config` fields tell OMS how to execute:
| Field | Meaning |
|-------|---------|
| `execution_name` | Hook identifier from the Erebor hook registry. |
| `execution_address` | Deployed hook contract on the target chain. |
| `target_address` | Yield source address. |
| `from_address` | Account that executes the intent. |
| `target_type` | Yield-source family, such as `erc4626` or `pendle_pt`. |
| `objective` | Routing preference: `MIN_SLIPPAGE`, `MIN_TIME`, or `BALANCED`. |
See [Action Config](/operate/strategy/action-config) for the full field reference.
## 3. Write the Trigger
The trigger is a `rules` tree. The simplest form is a single `EXPR` leaf that returns a boolean.
```json
{ "type": "EXPR", "expr": "vault_free_assets > 0.10 * vault_tvl" }
```
`vault_free_assets` is sourced live from chain as `IERC20(asset).balanceOf(strategy)` through the EVM RPC datafeed, so the trigger reflects the strategy contract's actual idle balance. `vault_tvl` is derived from yield-source allocations on the subgraph cadence. Both are in the underlying asset's base units.
Avoid division in trigger expressions. The validator dry-runs expressions against an empty snapshot where values can be zero. `vault_free_assets / vault_tvl > 0.10` can fail with division by zero at create/update time. Use the multiplicative form: `vault_free_assets > 0.10 * vault_tvl`.
See [Strategy DSL](/operate/strategy/dsl) for rule-tree syntax and variable notes.
## 4. Size the Action
`action_config.size_expr` is evaluated when the rule fires. It must return a positive number at runtime.
For the deposit half, deploy idle assets:
```text
vault_free_assets
```
For the withdrawal half, pull back the shortfall to the 7.5% target:
```text
0.075 * vault_tvl - vault_free_assets
```
Both expressions are positive when their corresponding trigger passes. A `size_expr` that evaluates to `0` or less is dropped before it reaches OMS.
## 5. Deposit Strategy JSON
These examples are adapted from `superman-strategy/testdata/cookbook/drift_rebalance_deposit.json` and `superman-strategy/testdata/cookbook/drift_rebalance_withdraw.json`, which are validated against `StrategyValidator` in the strategy test suite. Replace the vault ID, yield-source address, hook address, and executor address before sending.
```json
{
"id": "deposit-8453-0xdef0000000000000000000000000000000000000-yield-source-a",
"vault_id": "8453:0xdef0000000000000000000000000000000000000",
"name": "Deposit excess idle into Yield Source A",
"indicators": [],
"rules": {
"type": "EXPR",
"expr": "vault_free_assets > 0.10 * vault_tvl"
},
"action_config": {
"action": "DEPOSIT",
"size_expr": "vault_free_assets",
"objective": "BALANCED",
"execution_name": "ApproveAndDeposit4626VaultHook",
"execution_address": "0xdcAfC76B2f777bBA2d1e6C535F73AcA3dBe558F4",
"target_address": "0x0000000000000000000000000000000000000001",
"from_address": "0x41B8E24c97c64c1CC06c46f22DBa119f65603278",
"target_type": "erc4626",
"reason": "Free reserve above 10% of TVL - deploy idle into Yield Source A"
},
"conviction_config": { "mode": "BINARY" },
"risk_params": {},
"max_concurrent": 1
}
```
## 6. Withdrawal Strategy JSON
```json
{
"id": "withdraw-8453-0xdef0000000000000000000000000000000000000-yield-source-a",
"vault_id": "8453:0xdef0000000000000000000000000000000000000",
"name": "Withdraw from Yield Source A to replenish reserve",
"indicators": [],
"rules": {
"type": "EXPR",
"expr": "vault_free_assets < 0.05 * vault_tvl"
},
"action_config": {
"action": "WITHDRAWAL",
"size_expr": "0.075 * vault_tvl - vault_free_assets",
"objective": "BALANCED",
"execution_name": "ApproveAndWithdraw4626VaultHook",
"execution_address": "0xdcAfC76B2f777bBA2d1e6C535F73AcA3dBe558F4",
"target_address": "0x0000000000000000000000000000000000000001",
"from_address": "0x41B8E24c97c64c1CC06c46f22DBa119f65603278",
"target_type": "erc4626",
"reason": "Free reserve below 5% of TVL - pull liquidity back toward 7.5% target"
},
"conviction_config": { "mode": "BINARY" },
"risk_params": {},
"max_concurrent": 1
}
```
## 7. POST and Run
Each strategy is one create request:
```bash
curl -X POST https://strategy.superform.xyz/api/v1/strategies \
-H "Authorization: Bearer $SUPERFORM_JWT" \
-H "Content-Type: application/json" \
--data @deposit.json
```
Repeat with the withdrawal payload. A successful response returns the persisted strategy with `state: "CREATED"`.
Move each strategy to `RUNNING` when the vault has upkeep, merkle proof coverage, whitelisted yield sources, and OMS session-key readiness:
```bash
curl -X PATCH https://strategy.superform.xyz/api/v1/strategies/{strategy_id}/state \
-H "Authorization: Bearer $SUPERFORM_JWT" \
-H "Content-Type: application/json" \
--data '{"target_state": "RUNNING", "version": 1}'
```
See [Strategy Engine API](/operate/api/strategy) for endpoint details.
## What to Watch
| Surface | Healthy sign |
|---------|--------------|
| Strategy Canvas | Both strategies appear in their lanes with server-assigned priority and `RUNNING` state. |
| Dashboard | Idle reserve stays between the configured lower and upper bands over time. |
| Intent History | `DEPOSIT` intents appear when reserve is high and `WITHDRAWAL` intents appear when reserve is low. |
| Pause Operations | Emergency locks block only strategies that target the locked yield source. |
If creation, ticking, or execution fails, use [Strategy Troubleshooting](/operate/strategy/troubleshooting).
---
# Strategy DSL
Source: https://docs.superform.xyz/operate/strategy/dsl
The Strategy DSL has two layers:
- A boolean `rules` tree that decides whether a strategy should fire.
- Numeric expressions, such as `size_expr`, that decide how much the strategy should send to OMS.
## Rule Nodes
| Node | Meaning | Shape |
|------|---------|-------|
| `EXPR` | Evaluate one boolean expression. | `{ "type": "EXPR", "expr": "vault_free_assets > 0.10 * vault_tvl" }` |
| `AND` | All child rules must pass. | `{ "type": "AND", "children": [...] }` |
| `OR` | At least one child rule must pass. | `{ "type": "OR", "children": [...] }` |
| `NOT` | Negates one child rule. | `{ "type": "NOT", "child": {...} }` |
| `VOTE` | N-of-M child rules must pass. | `{ "type": "VOTE", "threshold": 2, "children": [...] }` |
Use shallow rule trees when possible. Deep trees are harder to review and usually indicate that two strategy lanes should be split.
## Expression Guidelines
Expressions are evaluated by the Strategy Engine against the current vault snapshot.
| Pattern | Use |
|---------|-----|
| `vault_free_assets > 0.10 * vault_tvl` | Prefer multiplication over division for ratios. |
| `ys_0xabc_alloc > 0` | Target source has deployed allocation. |
| `tick_age_seconds > 3600` | Enough time has elapsed since the last tick context. |
| `vault_pps_stale == 0` | Only run when PPS data is fresh. |
Avoid division in rule expressions unless the denominator is guaranteed non-zero. The validator dry-runs expressions against an empty snapshot to catch type and parse errors, which means division by zero can reject a strategy before it is saved.
## Common Variables
Variable availability depends on the datafeed and vault snapshot. Common families include:
| Family | Examples | Meaning |
|--------|----------|---------|
| Vault state | `vault_free_assets`, `vault_tvl`, `vault_total_supply` | Vault-level balances and supply. |
| PPS and timing | `vault_pps`, `vault_pps_age_seconds`, `vault_pps_stale` | Price-per-share state and freshness. |
| Yield source state | `ys__alloc`, `ys__apy`, `ys__tvl` | Source-specific allocation, yield, and liquidity. |
| Tick context | `tick_age_seconds`, `tick_block_number`, `tick_timestamp` | Engine tick timing and chain context. |
| Indicators | Custom aliases configured in `indicators` | Derived signals such as moving averages or momentum scores. |
Source-specific variables normalize addresses before aliasing. When in doubt, inspect the strategy validation error and the engine snapshot shown in [Intent History](/operate/ui/intent-history).
## `size_expr`
`action_config.size_expr` is numeric. It should evaluate to the amount OMS should act on, in the underlying asset's base units.
Rules for sizing:
- It must be positive at runtime.
- It should be bounded by available liquidity or allocation.
- It should match the action direction. Deposit sizes should not exceed idle assets; withdrawal sizes should not exceed source allocation.
- It should be easy to audit from dashboard numbers.
Examples:
```text
vault_free_assets
```
```text
min(vault_free_assets, 0.05 * vault_tvl)
```
```text
0.075 * vault_tvl - vault_free_assets
```
## Conviction Config
`conviction_config` controls how many passing ticks are required before a strategy publishes.
| Mode | Meaning |
|------|---------|
| `BINARY` | One passing evaluation is enough. |
| Scored/graded modes | Require stronger or repeated signal confidence when enabled by the engine configuration. |
Use `BINARY` for simple operational gates. Add stronger conviction only when false positives are expensive and delayed action is acceptable.
## Validation Timing
The Strategy Engine validates at create and update time:
- JSON shape and required fields.
- Known action names and lane compatibility.
- Expression parseability.
- Basic dry-run evaluation.
- Address formatting.
- Version conflicts on update.
Runtime checks still apply after validation. A strategy can save successfully and later fail to publish if it lacks a session key, merkle proof, active yield source, or executable OMS route.
---
# Action Config
Source: https://docs.superform.xyz/operate/strategy/action-config
`action_config` tells the Strategy Engine what OMS intent to publish when a strategy's rules pass.
## Fields
| Field | Required | Description |
|-------|----------|-------------|
| `action` | Yes | Strategy action, such as `DEPOSIT`, `WITHDRAWAL`, `CLAIM`, `REBALANCE`, `SWAP`, or `BRIDGE`. |
| `size_expr` | Yes | Numeric expression for the amount to execute, in underlying asset base units. |
| `objective` | Yes | Execution preference: `MIN_SLIPPAGE`, `MIN_TIME`, or `BALANCED`. |
| `execution_name` | Yes | Hook identifier from the hook registry. |
| `execution_address` | Yes | Hook contract address for the target chain. |
| `target_address` | Yes | Yield source, route target, or protocol address being acted on. |
| `from_address` | Yes | Account or executor address used for the intent. |
| `target_type` | Yes | Source family, such as `erc4626` or `pendle_pt`. |
| `execution_params` | Optional | Hook-specific encoded parameters. |
| `max_slippage_bps` | Optional | Maximum allowed slippage in basis points. |
| `reason` | Optional | Human-readable explanation shown in operator and audit surfaces. |
## Target Requirements
Before an action can execute, the target must be operationally allowed:
- The target yield source is whitelisted for the vault.
- The source has a compatible oracle where pricing is required.
- The hook exists in the active merkle config.
- The active merkle tree has been generated, synced onchain when required, and published for proof consumers.
- The vault has OMS/session-key readiness.
- The vault is not paused, and the target is not locked by an active emergency exit.
See [Yield Source Management](/operate/ui/yield-sources), [Merkle Trees](/operate/ui/merkle-trees), and [OMS API](/operate/api/oms).
## Action Notes
| Action | Notes |
|--------|-------|
| `DEPOSIT` | Usually targets a whitelisted yield source and sizes from idle assets. |
| `WITHDRAWAL` | Usually targets a source allocation and needs withdrawal hook proof coverage. |
| `CLAIM` | Claims rewards or proceeds when the protocol/source supports it. |
| `REBALANCE` | Moves allocation between sources; model the route and source authorization carefully. |
| `SWAP` | Requires route/slippage constraints and target authorization. |
| `BRIDGE` | Requires bridge route, destination-chain controls, and stricter incident monitoring. |
## Example
```json
{
"action": "DEPOSIT",
"size_expr": "min(vault_free_assets, 0.05 * vault_tvl)",
"objective": "BALANCED",
"execution_name": "ApproveAndDeposit4626VaultHook",
"execution_address": "0xdcAfC76B2f777bBA2d1e6C535F73AcA3dBe558F4",
"target_address": "0x0000000000000000000000000000000000000001",
"from_address": "0x41B8E24c97c64c1CC06c46f22DBa119f65603278",
"target_type": "erc4626",
"max_slippage_bps": 50,
"reason": "Deploy capped idle assets into the active ERC-4626 source"
}
```
## Operational Review
Before switching a strategy to `RUNNING`, review:
- The `size_expr` cannot consume more than the vault/source can safely supply.
- The action does not overlap with another strategy in the same lane unless the priority ordering is intentional.
- The target has emergency exit coverage if it can hold material liquidity.
- `max_slippage_bps` matches the liquidity profile of the action.
- The reason is clear enough for Incident History and postmortems.
---
# Strategy Troubleshooting
Source: https://docs.superform.xyz/operate/strategy/troubleshooting
Use this page when the Strategy Canvas shows an error, a strategy stays idle unexpectedly, or Intent History does not show the expected OMS execution.
## First Checks
| Question | Where to check |
|----------|----------------|
| Is the strategy `RUNNING`? | [Strategy Canvas](/operate/ui/strategy) or `GET /api/v1/strategies/{id}`. |
| Is the vault active? | [Setup Checklist](/operate/ui/setup-checklist) and [Vault Settings](/operate/ui/vault-settings). |
| Does OMS have a valid session key? | Strategy Canvas gate and [OMS API](/operate/api/oms#session-keys). |
| Is the target whitelisted? | [Yield Source Management](/operate/ui/yield-sources). |
| Is the hook authorized? | [Merkle Trees](/operate/ui/merkle-trees). |
| Is an emergency lock active? | [Pause Operations](/operate/ui/pause) and engine shard state. |
## Create or Update Rejected
| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `unknown identifier` | Expression references a variable absent from the snapshot. | Use known vault/source variables or add the indicator alias. |
| `division by zero` | Rule dry-run evaluated a zero denominator. | Rewrite ratios as multiplication comparisons. |
| `missing objective` | `action_config.objective` is absent. | Add `MIN_SLIPPAGE`, `MIN_TIME`, or `BALANCED`. |
| `invalid address` | Address is not hex/checksummed enough for the validator. | Replace placeholder or malformed addresses. |
| `409 Conflict` | Update used a stale `version`. | Refetch the strategy and retry with the current version. |
## Strategy Saves but Does Not Tick
| Cause | What to do |
|-------|------------|
| State is `CREATED` or `IDLE` | Transition to `RUNNING` after readiness checks. |
| Vault activation incomplete | Fund upkeep and clear setup checklist blockers. |
| Kill switch or vault pause active | Review Pause Operations and vault pause status. |
| Target emergency lock active | Stop the emergency exit only after the incident is resolved. |
| No shard state | Check Strategy Engine health and shard assignment for the vault. |
## Strategy Ticks but Does Not Publish
| Cause | What to do |
|-------|------------|
| Rule evaluates false | Inspect the signal snapshot in Intent History or engine logs. |
| `size_expr` is zero/negative | Rework sizing so it is positive when the trigger passes. |
| Missing session key | Grant or renew OMS session-key authorization. |
| Missing merkle proof | Generate, sync, and publish the active merkle tree. |
| Target not whitelisted | Add the yield source and oracle. |
| Max concurrency reached | Wait for in-flight intent completion or lower the strategy's overlap. |
## Intent Publishes but Does Not Fill
| Cause | What to do |
|-------|------------|
| OMS route failure | Inspect OMS events for planning or execution errors. |
| Hook transaction failed | Open the hook transaction in the explorer and compare calldata/proof. |
| Slippage too tight | Review `max_slippage_bps` against current liquidity. |
| Gas or nonce issue | Check executor wallet state and OMS event details. |
| Source liquidity changed | Confirm allocation, TVL, and withdrawal availability on the source. |
## Useful Endpoints
```bash
GET /api/v1/strategies/{id}
PATCH /api/v1/strategies/{id}/state
GET /api/v1/engine/shards/{vault_id}/state
GET /api/v1/intents?strategy_id={id}
GET /api/v1/intents/{intent_id}/events
GET /api/v1/intents/{intent_id}/fills
GET /api/v1/fills?strategy_id={id}
```
If the same strategy repeatedly fails after these checks, archive it, clone the payload into a new draft, simplify the rule tree, and reintroduce complexity one condition at a time.
---
# Strategy Anti-Patterns
Source: https://docs.superform.xyz/operate/strategy/anti-patterns
These patterns can pass validation but still create poor operator outcomes.
## Unbounded Sizing
Bad:
```text
vault_tvl
```
For a deposit, this can exceed idle assets. For a withdrawal, it can exceed target allocation.
Better:
```text
min(vault_free_assets, 0.05 * vault_tvl)
```
Bound sizing to the assets actually available for the action.
## Division in Triggers
Bad:
```text
vault_free_assets / vault_tvl > 0.10
```
This can fail validation on zero snapshots.
Better:
```text
vault_free_assets > 0.10 * vault_tvl
```
## Overlapping Lane Strategies
Two `DEPOSIT` strategies targeting the same source can both pass on the same tick and compete for idle assets.
Better:
- Split targets by rule.
- Use lane priority intentionally.
- Add `max_concurrent: 1`.
- Use mutually exclusive thresholds.
## No Cooldown on Noisy Signals
If a rule can oscillate around a boundary, it can create repeated small intents.
Better:
- Add a wider band between deposit and withdrawal thresholds.
- Require repeated ticks with conviction settings where appropriate.
- Set cooldowns for high-noise inputs.
## Treating Emergency Exit as a Normal Strategy
Emergency exits are incident controls. They create target-scoped locks and are operated from [Pause Operations](/operate/ui/pause).
Better:
- Keep normal withdrawal strategies focused on routine liquidity management.
- Use emergency exit only when source-level risk justifies a drain.
- Stop the emergency exit when the incident is resolved.
## Authorizing a Target After the Strategy
A strategy can save before the target is actually executable. It will not publish successfully without whitelist, oracle, merkle proof, and session-key readiness.
Better:
1. Whitelist the yield source.
2. Pair the oracle.
3. Generate, sync, and publish merkle proof coverage.
4. Confirm OMS/session-key readiness.
5. Move the strategy to `RUNNING`.
## Stale Priority Assumptions
Create and update requests do not own priority. Priority is server-managed through reorder.
Better:
- Create or update strategy content first.
- Use canvas drag-to-reorder or `POST /api/v1/strategies/reorder`.
- Always include every non-archived strategy in the lane when calling reorder.
## Manager-Only Assumptions in Public Docs
Public SuperVault detail pages are read-only. Do not document a public tab as though it can sign transactions.
Better:
- Use public detail pages for allocator inspection.
- Send operator actions to the authenticated Operate surface.
- Keep screenshots cropped so wallet state is not part of the documentation contract.
---
# Simulation
Source: https://docs.superform.xyz/operate/ui/simulation
Simulation sits between the Strategy Canvas and Intent History. Use it after authoring or reordering strategies, but before treating the live automation loop as production-ready.
The page is vault-scoped. Select the vault first, then choose whether to run a historical backtest or a forward-looking simulation against the current strategy set.
## Simulation Modes
| Mode | Endpoint | Use It For |
|------|----------|------------|
| Backtest | `POST /api/v1/simulations/backtest` | Replay strategy behavior over historical vault and yield-source state. |
| Forward simulation | `POST /api/v1/simulations/ahead` | Project how the current strategy set behaves from a chosen starting state. |
Backtests are best for validating trigger thresholds, action sizing, and lane ordering against known market history. Forward simulations are better for checking a proposed strategy set before the next keeper cycle.
## Operator Flow
1. Open **Operate → Simulation** with the target vault selected.
2. Choose backtest or forward simulation.
3. Select the strategy set, time range, and run parameters.
4. Run the simulation.
5. Review the chart, metrics, generated command timeline, and per-strategy results.
6. Save the run if it should become comparison evidence for future edits.
Simulation does not execute OMS intents or submit onchain transactions. It is a decision-support surface for strategy changes.
## Saved Simulations
Saved simulations let teams compare strategy edits over time without rerunning every scenario.
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/simulations/saved` | GET | List saved simulations for the current vault/user scope. |
| `/api/v1/simulations/saved/{simulation_id}` | GET | Load one saved simulation. |
| `/api/v1/simulations/saved/lookup` | POST | Check whether an equivalent simulation already exists. |
| `/api/v1/simulations/saved` | POST | Save a completed simulation result. |
| `/api/v1/simulations/saved/{simulation_id}` | DELETE | Delete a saved simulation. |
Use saved runs as review artifacts when changing thresholds, `size_expr` values, or lane order. They are especially useful when a strategy appears healthy in the canvas but produces too many small intents, creates oscillation, or misses a liquidity target.
## What to Inspect
| Surface | What to Check |
|---------|---------------|
| Trigger timing | Rules fire when the vault state actually crosses the intended threshold. |
| Action size | `size_expr` produces positive, reasonable amounts and does not overshoot the intended reserve. |
| Lane order | Higher-priority strategies win when multiple strategies are eligible. |
| Command timeline | Generated actions match the expected deposit, withdrawal, claim, swap, bridge, or rebalance sequence. |
| Error output | Validator, hook, merkle, or data gaps are visible before live execution. |
After a simulation looks correct, move to [Intent History](/operate/ui/intent-history) to monitor real OMS execution.
## API Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/simulations/backtest` | POST | Run a historical strategy simulation. |
| `/api/v1/simulations/ahead` | POST | Run a forward-looking simulation. |
| `/api/v1/simulations/saved` | GET | List saved simulations. |
| `/api/v1/simulations/saved/{simulation_id}` | GET | Read a saved simulation. |
| `/api/v1/simulations/saved/lookup` | POST | Find an existing saved simulation for the same inputs. |
| `/api/v1/simulations/saved` | POST | Persist a simulation result. |
| `/api/v1/simulations/saved/{simulation_id}` | DELETE | Delete a saved simulation. |
---
# Intent History
Source: https://docs.superform.xyz/operate/ui/intent-history

Intent History is the operator-facing execution explorer for Strategy and OMS activity. Use it to answer: what strategy fired, where the intent is in the lifecycle, what filled, what transaction mined, and why execution failed.
## What it shows
| View | Use it for |
|------|------------|
| Intent list | Search and filter intents across vaults and strategies. |
| Facets | Count by state, action, chain, vault, and strategy. |
| Lifecycle strip | See the current OMS lifecycle state and timestamps. |
| Fills table | Inspect per-fill venue, token in/out, amounts, gas, fees, and fill time. |
| OMS events | Audit message type, sequence, payload, and source. |
| Signal snapshot | See the Strategy Engine signal context that produced the intent. |
## Lifecycle states
The explorer uses the OMS lifecycle vocabulary:
`RECEIVED`, `ACKED`, `STAGED`, `PLANNED`, `EXECUTING`, `COMPLETED`, `ABORTED`, `EXPIRED`, `FAILED`, `CANCELED`, and `UNKNOWN` for rows that cannot be normalized.
## How it maps to OMS
The Strategy Engine projects OMS events and backfills into `strategy_intent_log`. Each row can include:
- `intent_id`, `execution_id`, `correlation_id`
- strategy and vault IDs
- last event sequence and message type
- ack/executing/completed/terminal timestamps
- error code and message
- total amount in/out
- total gas used
- fills count
- signal snapshot and summary
- source: `engine`, `nats`, or `backfill`
## API mapping
| Endpoint | Purpose |
|----------|---------|
| `GET /api/v1/intents` | Global intent search. |
| `GET /api/v1/intents/facets` | Facet counts for filters. |
| `GET /api/v1/intents/{intent_id}` | Intent detail. |
| `GET /api/v1/intents/{intent_id}/fills` | Fills for one intent. |
| `GET /api/v1/intents/{intent_id}/events` | OMS event audit rows. |
| `GET /api/v1/fills` | Global fill search. |
## Common reads
| Symptom | Check |
|---------|-------|
| Intent never mined | Look at `EXECUTING` events, hook transaction fields, and gas budget. |
| Intent completed with no visible position change | Check fills and amount out fields; some fields can be `null` until OMS hydration completes. |
| Strategy appears blocked | Check emergency locks, kill switch, session-key readiness, and merkle proof availability. |
| Duplicate-looking rows | Compare `intent_id`, `execution_id`, and `correlation_id`; retries and backfills can share context. |
---
# Yield Source Management
Source: https://docs.superform.xyz/operate/ui/yield-sources

Yield Source Management controls where a SuperVault is allowed to deploy capital. Each source is a protocol position, such as an ERC-4626 vault or Pendle PT, paired with an oracle that prices the position back into the vault's underlying asset.
Public detail pages expose Yield Sources as a read-only tab. All whitelist mutations happen in the authenticated Operate surface and use the prepare pattern: Erebor prepares calldata and the manager wallet signs the transaction.
## Page Layout
- **Whitelisted Yield Sources** — sources currently enabled for the selected vault.
- **Yield Source Registry** — global source catalog filtered by chain and active status.
- **Oracle Registry** — compatible pricing sources.
Registry entries can include provider data imported from external provider URLs, such as Morpho or Pendle metadata.
## Add a Yield Source
1. Select a registry yield source.
2. Select a compatible oracle.
3. Prepare the add transaction.
4. Sign with the manager wallet.
5. Confirm the source appears in the whitelisted table.
```bash
POST /api/v1/yield-sources/prepare-add
POST /api/v1/yield-sources/prepare-bulk-add
```
Adding a source also keeps the paired emergency-exit strategy lifecycle in sync. The app provisions one `EMERGENCY_EXIT` withdrawal strategy for the source when the active hook and proof prerequisites are available.
## Remove a Yield Source
```bash
POST /api/v1/yield-sources/prepare-remove
POST /api/v1/yield-sources/prepare-bulk-remove
```
Before removing, confirm the vault has no deployed liquidity in the source.
Removing a yield source does not unwind assets. Withdraw first, or use [Pause Operations](/operate/ui/pause) to arm an emergency drain when the situation requires immediate exit.
When a whitelisted source is removed, the paired `EMERGENCY_EXIT` strategy is archived. If an emergency lock is active for that source, removal is blocked until the operator stops the drain.
## Update Oracle
Use oracle updates when the current oracle is wrong, deprecated, or needs a safer pricing path.
```bash
POST /api/v1/yield-sources/prepare-update-oracle
POST /api/v1/yield-sources/prepare-bulk-update-oracle
```
## Source and Oracle Types
| Type | Meaning |
|------|---------|
| `erc4626` | Standard tokenized vault position |
| `pendle_pt` | Pendle principal token position |
| `erc4626` oracle | Prices by share value |
| `pendle_pt` oracle | Prices by PT market value |
| `pendle_pt_amortized` oracle | Amortizes PT value toward maturity |
## API Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/yield-sources/{chain_id}/{vault_address}` | GET | Whitelisted sources for a vault |
| `/api/v1/registry/yield-sources` | GET | Browse active registry sources for selection |
| `/api/v1/registry/oracles` | GET | Browse compatible oracles for selection |
| `/api/v1/yield-sources/prepare-add` | POST | Prepare add transaction |
| `/api/v1/yield-sources/prepare-remove` | POST | Prepare remove transaction |
| `/api/v1/yield-sources/prepare-update-oracle` | POST | Prepare oracle update transaction |
| `/api/v1/yield-sources/prepare-bulk-add` | POST | Prepare bulk add transaction |
| `/api/v1/yield-sources/prepare-bulk-remove` | POST | Prepare bulk remove transaction |
| `/api/v1/yield-sources/prepare-bulk-update-oracle` | POST | Prepare bulk oracle update transaction |
---
# Merkle Trees
Source: https://docs.superform.xyz/operate/ui/merkle-trees

Merkle Trees are the authorization layer for keeper and strategy execution. A keeper can only execute a hook when the hook call matches a leaf in an authorized merkle tree.
Public SuperVault detail pages expose hook and proof context as read-only inspection tabs. Drafting, activating, generating, syncing, and publishing remain manager workflows in Operate.
The operational flow is:
1. Draft a root configuration.
2. Activate the configuration version.
3. Generate a merkle tree from the active config.
4. Primary manager proposes / publishes the root onchain.
5. Publish the proof artifact for consumers.
## Hook Configs
Hook configs define which hook + parameter combinations the vault authorizes.
When creating a config, choose hooks from the registry and set inspect parameters such as:
- `yield_source` — target yield source address.
- `token` — token address.
- `oracle` — oracle address.
- Freeform hook parameters.
Each parameterized row becomes a merkle leaf. Keepers must supply the matching proof at execution time.
## Activate Config
Only one config version is active at a time. Activating a config selects the version used for the next tree generation.
Activating a config does not update the onchain root by itself. It selects what Erebor will generate next.
## Generate Tree
Generation is asynchronous:
```bash
POST /api/v1/merkle/generate
GET /api/v1/merkle/jobs/{job_id}
```
A `409 Conflict` means generation is already in progress for the vault.
The generated tree includes:
- Root hash.
- Leaf count.
- Config hash / version.
- Leaf details and proof paths.
## Sync Root Onchain
The Sync action compares generated tree state to onchain state and returns the next action:
| Action | Meaning |
|--------|---------|
| `in_sync` | Onchain root already matches |
| `needs_generation` | Generate a tree for the active config |
| `propose` | Sign a root proposal transaction |
| `pending_timelock` | Wait for the proposal timelock |
| `execute` | Sign execution after timelock expiry |
```bash
GET /api/v1/merkle/sync?vault_address={addr}&chain_id={id}
```
Strategies and emergency exits depend on active merkle withdrawal hooks. A yield source can be whitelisted but still unavailable for execution if the active merkle root does not authorize the required hook path.
## Publish Proof Artifact
Publishing makes the active proof set available to integrators, keepers, and frontends without Erebor authentication. The app exposes this as a primary-manager-gated **Publish** or **Re-publish** action on the active tree. Once the current artifact matches the active tree, the button state becomes **Published**.
```bash
POST /api/v1/merkle/publish
GET /api/v1/merkle/publish/jobs/{id}
GET /api/v1/merkle/published/{chain_id}/{vault}
```
Publishing is separate from syncing. Sync updates the protocol root; publish exposes the proof artifact. A published response includes:
| Field | Description |
|-------|-------------|
| `published` | Whether a published tree exists for the vault. |
| `published_at` | Timestamp for the most recent publish. |
| `root_hash` | Published merkle root. Verify this against the onchain root before using proofs. |
| `s3_url` | Public JSON artifact containing the full tree and leaf proofs. |
| `total_leaves` | Number of authorized leaf combinations. |
| `tree_id` | Published tree identifier. |
| `is_stale` | Whether the artifact no longer matches the current active tree. |
When `is_stale` is true, re-publish before relying on the artifact for execution. Even when `is_stale` is false, verify `root_hash` against the onchain SuperVaultAggregator state; offchain publish and onchain root sync are independent steps.
```bash
curl https://erebor.superform.xyz/api/v1/merkle/published/8453/{vault_address}
```
## Emergency Exit Dependency
Emergency liquidity exit requires an active withdrawal hook for the target yield source. If the active config has no matching withdrawal hook or the proof artifact is missing the leaf, the Pause page cannot arm that source until the merkle configuration is fixed.
## API Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/merkle/configs/versions` | GET | Config version history |
| `/api/v1/merkle/configs/version` | GET | Single version by number |
| `/api/v1/merkle/configs/active` | GET | Active config |
| `/api/v1/merkle/configs` | POST | Create new config |
| `/api/v1/merkle/configs/activate` | PUT | Activate a version |
| `/api/v1/merkle/trees` | GET | List trees for a vault |
| `/api/v1/merkle/proofs` | GET | List proofs |
| `/api/v1/merkle/proofs/lookup` | GET | Lookup one proof |
| `/api/v1/merkle/generate` | POST | Start generation |
| `/api/v1/merkle/jobs/{id}` | GET | Poll generation job |
| `/api/v1/merkle/sync` | GET | Determine root sync action |
| `/api/v1/merkle/publish` | POST | Publish active tree proof artifact |
| `/api/v1/merkle/publish/jobs/{id}` | GET | Poll publish job |
| `/api/v1/merkle/published/{chain_id}/{vault}` | GET | Public published tree metadata |
---
# Vault Settings
Source: https://docs.superform.xyz/operate/ui/vault-settings

Vault Settings is the configuration hub for vault-level controls. Most settings use the same pattern: Erebor prepares calldata, the manager wallet signs, and the protocol contract applies the change onchain.
Public SuperVault detail pages expose Vault Settings as read-only context. Manager-only controls, including upkeep deposit/withdrawal and settings transactions, remain in the authenticated Operate flow.
Prepare endpoints return transaction data. They do not mutate protocol state by themselves. The connected wallet must sign and submit the transaction.
## Access Model
Primary managers own sensitive vault settings. Secondary managers can operate the vault but do not control primary-only settings such as manager assignment and some security parameters.
See [Permissions Model](/operate/permissions) for the full matrix.
## Fees
Configure the vault's performance fee, management fee, and fee recipient.
Fee changes protect depositors through the protocol's proposal/timelock rules. The dashboard displays current values and prepares the required transaction when a primary manager proposes a change.
## PPS Parameters
Price-per-share controls define how often PPS can update and when stale data blocks operations.
Common fields:
- **Deviation threshold** — maximum allowed difference between calculated and current PPS.
- **PPS expiration / staleness** — when vault operations should stop waiting for a fresh PPS update.
- **Minimum update interval** — minimum time between PPS updates.
These settings are read from vault detail data and changed through signed transactions.
## Redeem Timelock
Redeem timelock adds a delay between redemption request and fulfillment eligibility.
Use it for:
- Liquidity sourcing before fulfillment.
- Fraud and risk review windows.
- Regulatory or operational policy requirements.
Relevant APIs:
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/redeem-timelocks` | GET | List configured timelocks |
| `/api/v1/redeem-timelocks/bulk` | GET | Bulk timelock lookup |
| `/api/v1/vaults/{chain_id}/{vault_address}/redeem-timelock` | GET | Read one vault's timelock |
| `/api/v1/settings/redeem-timelock` | POST | Prepare timelock update transaction |
Changing a redeem timelock affects future redemption requests. Existing requests are not retroactively changed.
## Banned Hook Leaves
A banned hook leaf is blocked even if it appears in an authorized merkle tree. Use this to quickly stop a specific hook + parameter combination while you prepare a cleaner merkle config update.
Banning a leaf is narrow: it blocks the exact leaf, not the entire hook category.
## Upkeep and Activation
Upkeep funds vault automation. It is also the practical activation gate for a new vault.
The Upkeep panel shows:
- Current upkeep balance.
- Upkeep token.
- Required balance and shortfall.
- Activation status: `active` or `insufficient_upkeep`.
- Whether the vault is active for keeper operations and discoverable in public listing surfaces.
- Pending upkeep withdrawal proposal, if any.
A vault below its required upkeep threshold is not considered active. Once the required balance is in place, Erebor marks it active and discoverable. Erebor returns this as `upkeep_info` plus `activation`; upkeep withdrawal lifecycle is returned separately as `withdrawal_status`.
### Deposit Upkeep
Managers deposit upkeep through the settings flow:
```bash
POST /api/v1/settings/upkeep/deposit
```
The dashboard handles token allowance checks first, then prepares the deposit transaction for wallet signature.
### Withdraw Upkeep
Withdrawals use a propose / execute pattern:
```bash
POST /api/v1/settings/upkeep/withdraw/propose
POST /api/v1/settings/upkeep/withdraw/execute
```
Use withdrawals carefully on live vaults. Pulling the balance below threshold can make the vault inactive or undiscoverable.
## Session Keys
Session keys delegate limited execution authority for automation. They are used by the OMS / Strategy Engine path so strategies can publish and execute intents without asking the manager wallet for every tick.
Erebor exposes vault-scoped session-key inventory and prepare endpoints:
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/session-keys` | GET | List session keys visible to the manager |
| `/api/v1/vaults/{chain_id}/{vault_address}/session-key` | GET | Read a vault session key |
| `/api/v1/vaults/session-key/prepare` | POST | Prepare grant transaction |
| `/api/v1/vaults/session-key/revoke/prepare` | POST | Prepare revoke transaction |
OMS also exposes strategy-automation session-key readiness and rotation endpoints. See [OMS API](/operate/api/oms).
## Notification Channels
Notification channels attach Slack, webhook, or websocket destinations to a vault.
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/notification-channels` | GET/POST | List or create manager-owned channels |
| `/api/v1/vaults/{chain_id}/{vault_address}/notification-channels` | GET | List vault channels |
| `/api/v1/vaults/{chain_id}/{vault_address}/notification-channels/{channel_id}` | POST | Attach channel |
| `/api/v1/vaults/{chain_id}/{vault_address}/notification-channels/{channel_id}` | DELETE | Detach channel |
Use notification channels for operational alerts, monitoring, and downstream automation.
## Alert Delivery
Alerts connect vault events to notification destinations. Treat alert delivery as operational plumbing: create the channel first, bind it to the vault, then verify delivery with a low-risk event before relying on it for incidents.
Notification channel records include:
| Field | Description |
|-------|-------------|
| `provider` | Channel provider or transport. |
| `name` | Operator-facing channel name. |
| `config` | Provider-specific destination config. Sensitive values should be redacted in UI. |
| `created_by` | Creator metadata. |
| `created_at` / `updated_at` | Audit timestamps. |
Recommended alert categories:
- Upkeep shortfall or activation loss.
- PPS staleness.
- Failed keeper runs.
- Session-key readiness changes.
- Emergency pause / unpause.
- Emergency liquidity exit arm / stop.
- Merkle generation or publish failure.
- Repeated OMS execution failure.
Troubleshooting:
| Symptom | Check |
|---------|-------|
| Channel exists but receives nothing | Confirm it is bound to the vault. |
| Sensitive destination appears in UI | Redact config display and rotate the destination if leaked. |
| Alerts are noisy | Tune event thresholds before muting the channel. |
| Incident alert is missing | Check audit logs and notification-channel binding first. |
## API Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/vaults/{chain_id}/{vault_address}` | GET | Full vault detail including fee, PPS, and upkeep data |
| `/api/v1/settings/fee-config` | GET | Read current fee config |
| `/api/v1/settings/fee-config/propose` | POST | Prepare fee-config proposal transaction |
| `/api/v1/settings/fee-config/execute` | POST | Prepare fee-config execution transaction |
| `/api/v1/settings/deviation-threshold` | GET | Read PPS deviation threshold |
| `/api/v1/settings/deviation-threshold/update` | POST | Prepare deviation-threshold update transaction |
| `/api/v1/settings/pps-expiration` | GET | Read PPS expiration / staleness setting |
| `/api/v1/settings/pps-expiration/propose` | POST | Prepare PPS-expiration proposal transaction |
| `/api/v1/settings/pps-expiration/execute` | POST | Prepare PPS-expiration execution transaction |
| `/api/v1/settings/pps-expiration/cancel` | POST | Prepare PPS-expiration cancellation transaction |
| `/api/v1/settings/redeem-timelock` | GET/PUT/DELETE | Read, update, or remove redeem timelock |
| `/api/v1/settings/banned-hook-leaves` | GET | Read banned hook leaves |
| `/api/v1/settings/banned-hook-leaves/update` | POST | Prepare banned-hook-leaf update transaction |
| `/api/v1/settings/min-update-interval` | GET | Read minimum PPS update interval |
| `/api/v1/settings/min-update-interval/propose` | POST | Prepare min-update-interval proposal transaction |
| `/api/v1/settings/min-update-interval/execute` | POST | Prepare min-update-interval execution transaction |
| `/api/v1/settings/min-update-interval/cancel` | POST | Prepare min-update-interval cancellation transaction |
| `/api/v1/settings/upkeep` | GET/POST | Read or prepare upkeep settings update |
| `/api/v1/settings/upkeep/deposit` | POST | Prepare upkeep deposit |
| `/api/v1/settings/upkeep/withdraw/propose` | POST | Prepare upkeep withdrawal proposal |
| `/api/v1/settings/upkeep/withdraw/execute` | POST | Prepare upkeep withdrawal execution |
---
# User Management
Source: https://docs.superform.xyz/operate/ui/user-management

This page controls vault access. Three tiers are managed here: primary manager (display only), secondary managers (onchain delegates), and view-only users (offchain read access). Primary manager access required.
The public SuperVault detail page exposes roles as read-only context. Adding or removing users requires the authenticated Operate surface and the appropriate manager role.
The full manager list is fetched from `GET /api/v1/users?vault_address={}&chain_id={}&limit=100`.
## Tab 1: Primary Manager
Displays the primary manager's wallet address and display name. Set at vault creation, immutable without governance-level contract interaction. This key is the root of trust for the vault.
## Tab 2: Secondary Managers
On-chain delegates with full operational access (except vault settings and user management).
### Adding
1. Click **Add Secondary Manager**
2. Enter wallet address
3. Click **Prepare** — `POST /api/v1/users/secondary-manager/prepare` → calldata
4. Sign the onchain transaction
### Removing
1. Click **Remove** — `POST /api/v1/users/secondary-manager/remove/prepare` → calldata
2. Sign the onchain transaction
### Updating Display Names
Click the pencil icon to update any user's display name (offchain, no gas):
```bash
PATCH /api/v1/users/{wallet_address}/display-name
{ "display_name": "Alice (Operations)" }
```
## Tab 3: View Only
Read-only dashboard access. No onchain transaction required.
```bash
# Add
POST /api/v1/users/view-only
{ "vault_address": "0x...", "chain_id": 8453, "wallet_address": "0x...", "display_name": "Risk Monitor" }
# Remove
DELETE /api/v1/users/view-only/{wallet_address}?vault_address={}&chain_id={}
```
## API Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/users` | GET | All users for vault |
| `/api/v1/users/secondary-manager/prepare` | POST | Prepare add tx |
| `/api/v1/users/secondary-manager/remove/prepare` | POST | Prepare remove tx |
| `/api/v1/users/view-only` | POST | Add view-only |
| `/api/v1/users/view-only/{wallet}` | DELETE | Remove view-only |
| `/api/v1/users/{wallet}/display-name` | PATCH | Update display name |
### User Object
```json
{
"user": {
"id": "uuid",
"wallet_address": "0x...",
"display_name": "Alice"
},
"roles": [
{
"vault_address": "0x...",
"chain_id": 8453,
"role": "secondary_manager",
"granted_by": "0xPrimary...",
"granted_at": "2025-01-15T10:30:00Z"
}
]
}
```
Add at least one secondary manager before going live. If you lose access to the primary key, secondary managers can continue vault operations.
---
# Distribute
Source: https://docs.superform.xyz/operate/ui/distribute

The Distribute section is the bridge between operating a vault and making it available through Superform surfaces. It shows vaults, providers, status, and actions that help operators confirm that a SuperVault is ready for allocators.
---
## Vault Table
**Route:** `/distribute`
The main Distribute view is a searchable vault table. Use it after the [Setup Checklist](/operate/ui/setup-checklist) to confirm that a vault has the metadata and availability needed for public distribution.
The table can include:
| Column | Description |
|--------|-------------|
| Name | Vault display name |
| Chain | Deployment chain |
| Symbol | Vault token symbol |
| Provider | Managed provider or issuer associated with the listing |
| TVL | Total value locked in USD |
| APY | Current annualized yield |
| Availability | Whether the vault is visible and available for distribution |
| Actions | View detail, continue listing, or open vault-specific operations |
## Distribution Readiness
Do not treat a created vault as distributable just because the contract exists. A launch-ready vault should have:
- Upkeep funded and activation cleared.
- Hooks authorized by the active merkle root.
- Yield sources whitelisted with compatible oracles.
- Strategy coverage for the intended deposit, withdrawal, or rebalance path.
- Distribution metadata with `listed` visibility and `available` status.
When the table shows a vault as unavailable, return to [Setup Checklist](/operate/ui/setup-checklist) before routing depositor traffic.
## Provider Actions
Provider actions are used by distribution operators to keep listing metadata accurate. Depending on access, the UI can expose provider creation, provider review, or continuation actions for incomplete listings.
The staging account used for these docs did not have access to provider approval queues, so this page documents the visible Distribute surface and avoids internal-only registry maintenance tools.
## API Reference
Vault list data comes from `GET /api/v1/public/vaults` for unauthenticated/public surfaces and `GET /api/v1/vaults` for authenticated operator views. Yield source, oracle, hook, and provider metadata comes from the Erebor registry endpoints.
| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| `GET /api/v1/public/vaults` | GET | None | Public vault list (no auth, used on Home page) |
| `GET /api/v1/vaults` | GET | JWT | Authenticated vault list (`?detail=minimal&limit=500`) |
| `GET /api/v1/registry/yield-sources` | GET | JWT | List yield sources (`?chain_id={}&is_active=true`) |
| `GET /api/v1/registry/oracles` | GET | JWT | List oracles |
---
# Pause Operations
Source: https://docs.superform.xyz/operate/ui/pause

The Pause page is the operator safety console. It has three distinct controls:
1. **Pause Vault** — onchain contract-level pause / unpause.
2. **Pause Keepers** — offchain per-vault service controls.
3. **Emergency Exit** — per-yield-source liquidity drain through the Strategy Engine and OMS.
Use the narrowest control that solves the problem. A vault pause, keeper pause, and emergency exit have different blast radii.
## Pause Vault
Vault pause is an onchain control. It halts protocol-level vault operations until a manager signs an unpause transaction.
```bash
GET /api/v1/vaults/pause-status?vaults={chainId}:{vaultAddr}
POST /api/v1/vaults/emergency-pause/prepare
POST /api/v1/vaults/emergency-unpause/prepare
```
Pause reasons are recorded for auditability.
## Pause Keepers
Keeper service controls are offchain. Use them when you want to stop a worker without changing vault contract state.
```bash
GET /api/v1/vaults/{chain_id}/{vault}/services
PUT /api/v1/vaults/{chain_id}/{vault}/services/{service_name}/pause
PUT /api/v1/vaults/{chain_id}/{vault}/services/{service_name}/resume
PUT /api/v1/vaults/{chain_id}/{vault}/services/enabled
```
Service pause is useful for maintenance, data issues, or temporary operator intervention.
## Emergency Exit
Emergency Exit drains liquidity from one whitelisted yield source. It is operator-armed, target-scoped, and designed for urgent source-level risk events.
When armed for a source:
- The paired `EMERGENCY_EXIT` strategy transitions to `RUNNING`.
- The Strategy Engine records an emergency lock keyed by that source's `target_address`.
- User-defined strategies targeting the same address are blocked from publishing intents.
- Strategies targeting other yield sources continue normally unless they have their own lock.
- The emergency strategy emits withdrawal intents on every tick until stopped.
- In-flight OMS transactions continue to settle even after the operator stops the drain.
Emergency Exit is not a replacement for vault pause. If the entire vault is unsafe, pause the vault. If one source is unsafe and the vault can keep operating elsewhere, arm an exit for that source.
### Prerequisites
Emergency Exit depends on:
- The yield source being whitelisted.
- An active withdrawal hook for that source in the current merkle config.
- Published / available strategy proofs for the active merkle tree.
- A paired `EMERGENCY_EXIT` strategy, created automatically when prerequisites are available.
- OMS/session-key readiness for execution.
If the Pause page cannot arm a source, check [Merkle Trees](/operate/ui/merkle-trees), [Yield Source Management](/operate/ui/yield-sources), and [Strategy Canvas](/operate/ui/strategy).
### Arm an Emergency Exit
1. Open **Pause → Emergency Exit**.
2. Select the yield source.
3. Review current allocation, APY/TVL metadata, and blocked strategies.
4. Enter the confirmation suffix for the yield source address.
5. Optionally enter a reason.
6. Choose max slippage. Default is **50 bps**.
7. Click **Arm emergency exit**.
The reason is stored with the emergency arm request and shown while the lock is active.
If manager permission is revoked while arming, the in-flight request is not automatically cancelled. Verify the Pause page and stop the drain if it succeeded under the prior policy.
### Stop an Emergency Exit
Stopping transitions the paired strategy back to `IDLE` and releases the emergency lock. User-defined strategies targeting that source are eligible again on the next tick.
```bash
PATCH /api/v1/strategies/{strategy_id}/state
{
"target_state": "IDLE",
"version": 12
}
```
Stopping does not cancel transactions already published to the OMS.
## Strategy Canvas Locking
While an emergency lock is active:
- The Strategy Canvas shows a banner for active drains.
- Strategy toggles that target the locked source are disabled.
- Reorder controls are disabled only for lanes containing affected strategies.
- Emergency strategies are hidden from normal strategy editing by default and are read-only when shown.
The engine exposes locks through shard and vault state as `emergency_locks` entries with `strategy_id`, `target_address`, `started_at`, and optional `reason`.
## Yield-Source Whitelist Side Effects
Emergency Exit is tied to yield-source whitelist state.
| Action | Side Effect |
|--------|-------------|
| Whitelist add | Provisions a paired `EMERGENCY_EXIT` withdrawal strategy for that target when prerequisites are available. |
| Whitelist remove | Archives the paired emergency strategy when no lock is active. |
| Remove while locked | Blocked until the drain is stopped and the lock clears. |
## Failure States
| State | Meaning |
|-------|---------|
| No withdrawal hook or proof | Active merkle tree does not authorize the drain. Configure hooks and publish proofs. |
| `EMERGENCY_LOCK_ACTIVE` | Another emergency drain already holds the target lock. |
| Duplicate target | A paired emergency strategy already exists for that vault / target. |
| Read-only banner | Current operator can inspect but cannot arm or stop drains. |
| Lock data unavailable | UI fails closed and blocks controls until lock state loads. |
## Control Comparison
| Control | Scope | Onchain Tx? | Typical Use |
|---------|-------|-------------|-------------|
| Pause Vault | Entire vault contract | Yes | Protocol-level emergency |
| Pause Keepers | One or all offchain services | No | Maintenance or worker incident |
| Emergency Exit | One yield source target | Strategy/OMS execution | Drain a risky source |
## API Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/vaults/pause-status` | GET | Batch onchain pause status |
| `/api/v1/vaults/emergency-pause/prepare` | POST | Prepare pause transaction |
| `/api/v1/vaults/emergency-unpause/prepare` | POST | Prepare unpause transaction |
| `/api/v1/vaults/{chain_id}/{vault}/services` | GET | List keeper services |
| `/api/v1/vaults/{chain_id}/{vault}/services/{name}/pause` | PUT | Pause one service |
| `/api/v1/vaults/{chain_id}/{vault}/services/{name}/resume` | PUT | Resume one service |
| `/api/v1/vaults/{chain_id}/{vault}/services/enabled` | PUT | Bulk enable / disable services |
| `/api/v1/strategies?kind=EMERGENCY_EXIT` | GET | List paired emergency strategies |
| `/api/v1/strategies/{id}/state` | PATCH | Arm or stop paired emergency strategy |
| `/api/v1/engine/shards/{vault_id}/state` | GET | Read active emergency locks and allocations |
---
# Automating SuperformOS
Source: https://docs.superform.xyz/operate/automation/overview
Automated operators monitor vault state 24/7, define rule-based strategies via the Strategy Canvas, pause services on anomaly detection, and execute operations without human latency. SuperVaults supports full programmatic control via the Erebor management API and the Strategy Engine.
## Authentication for Automation
Generate a JWT without the browser:
1. Sign an EIP-712 auth message with your wallet's private key
2. Submit the signature to Dynamic.xyz to receive a JWT
3. Pass the JWT as `Authorization: Bearer ` on all requests
Verify against Erebor:
```python Python
import requests
EREBOR_URL = "https://erebor.superform.xyz"
JWT = ""
headers = {"Authorization": f"Bearer {JWT}"}
resp = requests.get(f"{EREBOR_URL}/api/v1/auth/me", headers=headers)
resp.raise_for_status()
user = resp.json()
print(f"Authenticated as: {user['wallet_address']}")
print(f"Vaults: {[v['vault_address'] for v in user['vaults']]}")
```
```typescript TypeScript
const EREBOR_URL = "https://erebor.superform.xyz";
const JWT = process.env.SUPERFORMOS_JWT!;
const headers = { Authorization: `Bearer ${JWT}` };
const resp = await fetch(`${EREBOR_URL}/api/v1/auth/me`, { headers });
if (!resp.ok) throw new Error(`Auth failed: ${resp.status}`);
const user = await resp.json();
console.log(`Authenticated as: ${user.wallet_address}`);
console.log(`Vaults: ${user.vaults.map((v: any) => v.vault_address)}`);
```
## Safety Principles
Automated scripts have full manager-level access. A bug can modify live vault configuration. Apply these safeguards before deploying.
### Test on Base First
Base (chain ID: 8453) has lower gas costs and faster confirmation times. Validate all automation against Base vaults before Ethereum mainnet.
### Rate Limit Polling
| Data | Recommended Interval |
|------|---------------------|
| Vault state | 15s |
| Analytics overview | 60s |
| Service health | 30s |
### Circuit Breakers
Stop automation on consecutive API failures:
```python
MAX_CONSECUTIVE_ERRORS = 5
consecutive_errors = 0
def safe_api_call(fn):
global consecutive_errors
try:
result = fn()
consecutive_errors = 0
return result
except Exception as e:
consecutive_errors += 1
if consecutive_errors >= MAX_CONSECUTIVE_ERRORS:
alert_and_stop(f"Circuit breaker: {consecutive_errors} consecutive errors")
raise
```
### Validate Before Writing
Before pushing strategy or configuration changes:
1. Fetch current state
2. Validate the proposed change is meaningful
3. Confirm yield sources are all still whitelisted
4. Apply change
5. Re-fetch and verify the change was applied
## Example: Strategy-Driven Vault Monitoring
Fetches vault state from Erebor and checks key health indicators:
```python Python
import requests
import time
EREBOR_URL = "https://erebor.superform.xyz"
JWT = ""
CHAIN_ID = 8453
VAULT_ADDRESS = "0xYourVault..."
headers = {"Authorization": f"Bearer {JWT}"}
def get_vault_state():
resp = requests.get(
f"{EREBOR_URL}/api/v1/vaults/{CHAIN_ID}/{VAULT_ADDRESS}",
headers=headers
)
resp.raise_for_status()
return resp.json()
def check_vault_health():
vault = get_vault_state()
# Check PPS staleness
remaining = vault.get("remaining_staleness_time", 9999)
if remaining < 3600:
print(f"WARNING: PPS expires in {remaining // 60} minutes")
# Check pause status
if vault.get("is_paused"):
print("CRITICAL: Vault is paused onchain")
# Check TVL
tvl = vault.get("tvl_usd", 0)
print(f"TVL: ${tvl:,.0f} | PPS staleness: {remaining}s")
if __name__ == "__main__":
while True:
try:
check_vault_health()
except Exception as e:
print(f"Error: {e}")
time.sleep(60)
```
```typescript TypeScript
const EREBOR_URL = "https://erebor.superform.xyz";
const JWT = process.env.SUPERFORMOS_JWT!;
const CHAIN_ID = 8453;
const VAULT_ADDRESS = "0xYourVault...";
const headers = { Authorization: `Bearer ${JWT}` };
async function getVaultState() {
const resp = await fetch(
`${EREBOR_URL}/api/v1/vaults/${CHAIN_ID}/${VAULT_ADDRESS}`,
{ headers }
);
if (!resp.ok) throw new Error(`Vault fetch failed: ${resp.status}`);
return resp.json();
}
async function checkVaultHealth() {
const vault = await getVaultState();
const remaining = vault.remaining_staleness_time ?? 9999;
if (remaining < 3600) {
console.warn(`WARNING: PPS expires in ${Math.floor(remaining / 60)} minutes`);
}
if (vault.is_paused) {
console.error("CRITICAL: Vault is paused onchain");
}
console.log(`TVL: $${vault.tvl_usd?.toLocaleString()} | Staleness: ${remaining}s`);
}
setInterval(checkVaultHealth, 60_000);
```
## Next Steps
Poll vault state and build alert conditions.
Audit logs as interim event stream.
---
# Monitoring Vault State
Source: https://docs.superform.xyz/operate/automation/monitoring
SuperVaults uses a polling-first monitoring model. No webhook system is implemented yet. Use these intervals as a baseline:
| Data | Endpoint | Interval |
|------|----------|----------|
| Vault state (PPS, pause, supply) | `GET /api/v1/vaults/{chain_id}/{address}` | 15s |
| Analytics overview (APY, TVL) | `GET .../analytics/overview` | 60s |
| Risk metrics | `GET .../analytics/risk` | 120s |
| Service health | `GET /api/v1/vaults/{chain_id}/{vault}/services` | 30s |
| Infrastructure health | `GET /health` | 30s |
## Key Metrics
### PPS Staleness
```python
def check_pps_staleness(vault):
remaining = vault.get("remaining_staleness_time", 0)
if remaining < 3600: # <1 hour
alert("WARNING: PPS staleness — less than 1 hour remaining")
if remaining < 600: # <10 minutes
alert("CRITICAL: PPS about to expire")
```
### Service Success Rate
```python
def check_service_health(services):
for svc in services:
rate = svc["success_rate"]
if rate < 0.80:
alert(f"CRITICAL: {svc['name']} success rate {rate:.0%}")
elif rate < 0.95:
alert(f"WARNING: {svc['name']} success rate {rate:.0%}")
```
### Redemption Queue Depth
```python
def check_redemption_queue(executions):
pending = [e for e in executions if e["status"] == "pending"]
if len(pending) > 10:
alert(f"WARNING: {len(pending)} pending redemptions in queue")
```
## Full Monitoring Loop
```python Python
import requests
import time
import logging
EREBOR_URL = "https://erebor.superform.xyz"
JWT = ""
CHAIN_ID = 8453
VAULT = "0xYourVault..."
headers = {"Authorization": f"Bearer {JWT}"}
log = logging.getLogger(__name__)
def fetch_vault_state():
r = requests.get(
f"{EREBOR_URL}/api/v1/vaults/{CHAIN_ID}/{VAULT}",
headers=headers, timeout=10
)
r.raise_for_status()
return r.json()
def fetch_service_health():
r = requests.get(
f"{EREBOR_URL}/api/v1/vaults/{CHAIN_ID}/{VAULT}/services",
headers=headers, timeout=10
)
r.raise_for_status()
return r.json()
def alert(severity: str, message: str):
"""Replace with Slack/PagerDuty integration."""
log.warning(f"[{severity}] {message}")
def run_checks():
# Vault state
try:
vault = fetch_vault_state()
if vault.get("is_paused"):
alert("CRITICAL", f"Vault {VAULT} is paused onchain")
remaining = vault.get("remaining_staleness_time", 9999)
if remaining < 600:
alert("CRITICAL", f"PPS expires in {remaining}s")
elif remaining < 3600:
alert("WARNING", f"PPS expires in {remaining // 60}min")
tvl = vault.get("tvl_usd", 0)
log.info(f"TVL: ${tvl:,.0f} | PPS: {vault.get('current_pps')} | Staleness: {remaining}s")
except Exception as e:
alert("ERROR", f"Failed to fetch vault state: {e}")
# Services
try:
services = fetch_service_health()
for svc in services:
rate = svc.get("success_rate", 1.0)
if rate < 0.80:
alert("CRITICAL", f"Service {svc['name']}: {rate:.0%} success rate")
elif rate < 0.95:
alert("WARNING", f"Service {svc['name']}: {rate:.0%} success rate")
except Exception as e:
alert("ERROR", f"Failed to fetch service health: {e}")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
while True:
run_checks()
time.sleep(30)
```
```typescript TypeScript
const EREBOR_URL = "https://erebor.superform.xyz";
const JWT = process.env.SUPERFORMOS_JWT!;
const CHAIN_ID = 8453;
const VAULT = "0xYourVault...";
const headers = { Authorization: `Bearer ${JWT}` };
async function fetchWithTimeout(url: string, timeoutMs = 10000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeoutMs);
try {
const resp = await fetch(url, { headers, signal: controller.signal });
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
return resp.json();
} finally {
clearTimeout(id);
}
}
function alert(severity: string, message: string) {
console.warn(`[${severity}] ${message}`);
}
async function runChecks() {
try {
const vault = await fetchWithTimeout(
`${EREBOR_URL}/api/v1/vaults/${CHAIN_ID}/${VAULT}`
);
if (vault.is_paused) alert("CRITICAL", `Vault ${VAULT} is paused`);
const remaining = vault.remaining_staleness_time ?? 9999;
if (remaining < 600) alert("CRITICAL", `PPS expires in ${remaining}s`);
else if (remaining < 3600) alert("WARNING", `PPS expires in ${Math.floor(remaining / 60)}min`);
console.log(`TVL: $${vault.tvl_usd?.toLocaleString()} | Staleness: ${remaining}s`);
} catch (e) {
alert("ERROR", `Vault state fetch failed: ${e}`);
}
try {
const services = await fetchWithTimeout(
`${EREBOR_URL}/api/v1/vaults/${CHAIN_ID}/${VAULT}/services`
);
for (const svc of services) {
if (svc.success_rate < 0.80) alert("CRITICAL", `${svc.name}: ${(svc.success_rate * 100).toFixed(0)}% success`);
else if (svc.success_rate < 0.95) alert("WARNING", `${svc.name}: ${(svc.success_rate * 100).toFixed(0)}% success`);
}
} catch (e) {
alert("ERROR", `Service health fetch failed: ${e}`);
}
}
setInterval(runChecks, 30_000);
```
## Alert Integrations
Replace the `alert()` stub with your notification system:
### Slack
```python
import httpx
def send_slack(message: str):
httpx.post(
os.environ["SLACK_WEBHOOK_URL"],
json={"text": message}
)
```
### PagerDuty
```python
import httpx
def pagerduty_alert(summary: str, severity: str = "warning"):
httpx.post(
"https://events.pagerduty.com/v2/enqueue",
json={
"routing_key": os.environ["PAGERDUTY_ROUTING_KEY"],
"event_action": "trigger",
"payload": {
"summary": summary,
"severity": severity,
"source": "supervaults-monitor"
}
}
)
```
---
# Notifications and Event Streams
Source: https://docs.superform.xyz/operate/automation/webhooks
Operate exposes two event surfaces for operators:
1. **Notification channels** for routed operational alerts.
2. **Audit logs and polling** for integration-grade event reconstruction.
Use notification channels for humans and incident destinations. Use audit logs / state polling for deterministic integrations.
## Notification Channels
Create a channel, then bind it to the vaults that should send alerts.
```bash
# List or create channels
GET /api/v1/notification-channels
POST /api/v1/notification-channels
# List or bind channels for one vault
GET /api/v1/vaults/{chain_id}/{vault_address}/notification-channels
POST /api/v1/vaults/{chain_id}/{vault_address}/notification-channels/{channel_id}
# Unbind a channel
DELETE /api/v1/vaults/{chain_id}/{vault_address}/notification-channels/{channel_id}
```
Recommended alert categories:
- Upkeep shortfall or activation loss.
- PPS staleness.
- Failed keeper runs.
- Session-key readiness changes.
- Emergency pause / unpause.
- Emergency liquidity exit arm / stop.
- Merkle generation or publish failure.
- Repeated OMS execution failure.
See [Vault Settings](/operate/ui/vault-settings#alert-delivery) for the operator flow.
## Audit Logs as an Event Stream
The audit log endpoint records vault operations and manager actions:
```bash
GET /api/v1/audit/logs
Authorization: Bearer
# Query params:
# ?vault_address={addr}&chain_id={id}
# &limit=100&offset=0
# &action_type={type}
# &from_timestamp={iso}&to_timestamp={iso}
```
```json
{
"logs": [
{
"id": "uuid",
"timestamp": "2026-05-09T10:00:00Z",
"action_type": "yield_source_add",
"vault_address": "0x...",
"chain_id": 8453,
"actor_wallet": "0xManager...",
"details": {
"yield_source": "0xMorphoVault...",
"oracle": "0xOracle..."
}
}
],
"total": 48,
"has_more": false
}
```
Export when you need a point-in-time compliance artifact:
```bash
GET /api/v1/audit/export
Authorization: Bearer
```
## Polling Pattern
```python
import requests
import time
from datetime import datetime, timezone
EREBOR_URL = "https://erebor.superform.xyz"
headers = {"Authorization": "Bearer "}
CHAIN_ID = 8453
VAULT = "0xYourVault..."
def poll_audit_logs(since: datetime):
resp = requests.get(
f"{EREBOR_URL}/api/v1/audit/logs",
headers=headers,
params={
"vault_address": VAULT,
"chain_id": CHAIN_ID,
"from_timestamp": since.isoformat(),
"limit": 100,
},
timeout=15,
)
resp.raise_for_status()
return resp.json().get("logs", [])
last_check = datetime.now(timezone.utc)
while True:
new_events = poll_audit_logs(since=last_check)
for event in new_events:
print(f"New event: {event['action_type']} by {event['actor_wallet']}")
# Process event...
if new_events:
last_check = datetime.now(timezone.utc)
time.sleep(60)
```
## State Polling Map
| Need | Source |
|------|--------|
| Vault state changes | `GET /api/v1/vaults/{chain_id}/{address}` |
| Service degradation | `GET /api/v1/vaults/{chain_id}/{vault}/services` |
| Operation events | `GET /api/v1/audit/logs` |
| PPS staleness | Vault detail PPS timestamps and remaining staleness |
| Intent execution | `GET /api/v1/intents` and OMS event/fill endpoints |
| Emergency locks | `GET /api/v1/engine/shards/{vault_id}/state` |
See [Monitoring Vault State](/operate/automation/monitoring) for production-ready polling examples.
---
# Operate API Overview
Source: https://docs.superform.xyz/operate/api/overview
Operate is the operator control plane for SuperVaults. Its APIs split by operational responsibility.
## Services
| Service | Owns | Docs |
|---------|------|------|
| Erebor | Vault reads, settings, registries, merkle config, upkeep, users, pause controls | [Erebor API](/operate/api/erebor) |
| Strategy Engine | Strategy lifecycle, ordering, live shards, intent projections, emergency locks | [Strategy API](/operate/api/strategy) |
| OMS | DeFiX intent execution, events, session-key readiness, hook transactions | [OMS API](/operate/api/oms) |
## Auth
Authenticated endpoints use Dynamic-issued bearer JWTs:
```bash
Authorization: Bearer
```
Public endpoints, such as published merkle artifacts and public vault listings, do not require an operator JWT.
## Prepare, Sign, Confirm
Most protocol mutations follow this model:
1. API prepares calldata.
2. Wallet signs and submits the transaction.
3. API / indexer reads resulting state or confirms a transaction hash.
Do not treat a successful prepare request as a completed setting change.
## Vault Identity and Listing State
A vault can be:
- Created onchain.
- Indexed in Erebor.
- Active / discoverable after upkeep is funded.
- Visible and available in Superform distribution surfaces.
Those are related but separate states. The dashboard setup flow tracks them together so operators do not route users into a half-configured vault.
## Strategy Execution Flow
1. Operator configures hooks and publishes merkle authorization.
2. Operator whitelists yield sources and pairs oracles.
3. Operator creates and orders strategies.
4. Strategy Engine evaluates rules and submits DeFiX intents.
5. OMS validates authorization and executes hook transactions.
6. Events and fills flow back into intent history.
## API Reference Pages
- [Erebor API](/operate/api/erebor)
- [Strategy Engine API](/operate/api/strategy)
- [OMS API](/operate/api/oms)
---
# Erebor API Reference
Source: https://docs.superform.xyz/operate/api/erebor
Erebor is the SuperVaults management API. It powers vault reads, settings preparation, registries, merkle workflows, upkeep, users, pause controls, notification channels, and public vault data.
**Base URL:** `https://erebor.superform.xyz`
**Auth:** `Authorization: Bearer ` except public endpoints
For onchain changes, Erebor prepares calldata and the manager wallet signs the transaction. The REST request is not the state change; the signed transaction is.
## Auth
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/auth/me` | GET | Current user, wallet, and vault roles |
| `/api/v1/auth/verify` | GET | Lightweight JWT verification |
## Vaults and Public Discovery
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/public/vaults` | GET | Public vault list |
| `/api/v1/vaults` | GET | Authenticated vault list |
| `/api/v1/vaults/{chain_id}/{vault_address}` | GET | Full vault detail |
Vault detail includes onchain identity, managers, fees, PPS, balances, TVL, upkeep information, activation state, and allocation data when available. Authenticated list responses may include `activation`; public vault responses include `upkeep_info` and `activation` when Erebor can compute them.
Public discovery depends on more than deployment. Erebor only surfaces vaults with known activation when `activation.is_discoverable` is true. If activation is unknown because a subgraph dependency is unavailable, the vault can still be returned with `activation` unset rather than treated as deleted.
## Vault Creation
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/vaults/create/prepare` | POST | Prepare vault creation transaction |
| `/api/v1/vaults/create/confirm` | POST | Confirm creation transaction hash and start indexing |
| `/api/v1/vaults/create/jobs/{id}` | GET | Poll creation job |
`ConfirmCreateVaultRequest` can include `vault_config.redeem_timelock_seconds` for post-indexing settings.
Distribution metadata is handled by the dashboard/listing layer after vault address resolution. Onchain creation and Superform availability are separate states.
## Settings and Upkeep
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/settings/fee-config` | GET | Read current fee config |
| `/api/v1/settings/fee-config/propose` | POST | Prepare fee-config proposal transaction |
| `/api/v1/settings/fee-config/execute` | POST | Prepare fee-config execution transaction |
| `/api/v1/settings/deviation-threshold` | GET | Read PPS deviation threshold |
| `/api/v1/settings/deviation-threshold/update` | POST | Prepare deviation-threshold update transaction |
| `/api/v1/settings/pps-expiration` | GET | Read PPS expiration / staleness setting |
| `/api/v1/settings/pps-expiration/propose` | POST | Prepare PPS-expiration proposal transaction |
| `/api/v1/settings/pps-expiration/execute` | POST | Prepare PPS-expiration execution transaction |
| `/api/v1/settings/pps-expiration/cancel` | POST | Prepare PPS-expiration cancellation transaction |
| `/api/v1/settings/redeem-timelock` | GET/PUT/DELETE | Read, update, or remove redeem timelock |
| `/api/v1/settings/banned-hook-leaves` | GET | Read banned hook leaves |
| `/api/v1/settings/banned-hook-leaves/update` | POST | Prepare banned-hook-leaf update transaction |
| `/api/v1/settings/min-update-interval` | GET | Read minimum PPS update interval |
| `/api/v1/settings/min-update-interval/propose` | POST | Prepare min-update-interval proposal transaction |
| `/api/v1/settings/min-update-interval/execute` | POST | Prepare min-update-interval execution transaction |
| `/api/v1/settings/min-update-interval/cancel` | POST | Prepare min-update-interval cancellation transaction |
| `/api/v1/settings/upkeep` | GET/POST | Read or prepare upkeep settings update |
| `/api/v1/settings/upkeep/deposit` | POST | Prepare upkeep deposit |
| `/api/v1/settings/upkeep/withdraw/propose` | POST | Prepare upkeep withdrawal proposal |
| `/api/v1/settings/upkeep/withdraw/execute` | POST | Prepare upkeep withdrawal execution |
| `/api/v1/redeem-timelocks` | GET | List redeem timelocks |
| `/api/v1/redeem-timelocks/bulk` | GET | Bulk redeem timelock lookup |
| `/api/v1/vaults/{chain_id}/{vault_address}/redeem-timelock` | GET | Read one vault's redeem timelock |
Upkeep data drives activation and discoverability in the dashboard. Operators should treat insufficient upkeep as a launch blocker.
Current upkeep responses use:
| Field | Description |
|-------|-------------|
| `upkeep_info.balance` | Current upkeep balance in raw token units. |
| `upkeep_info.token` | Upkeep token address on settings responses. |
| `upkeep_info.threshold.required_balance` | Required upkeep balance for activation. |
| `upkeep_info.threshold.shortfall` | Remaining amount needed; `0` means the threshold is satisfied. |
| `activation.is_active` | Whether the vault is eligible for keeper operations. |
| `activation.is_discoverable` | Whether the vault is surfaced on public listing responses. |
| `activation.status` | `active` or `insufficient_upkeep`. |
| `activation.reason` | Optional explanation when inactive. |
| `withdrawal_status` | Pending withdrawal lifecycle; separate from `activation.status`. |
## Merkle Authorization
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/merkle/configs/versions` | GET | Config version history |
| `/api/v1/merkle/configs/version` | GET | Single config version |
| `/api/v1/merkle/configs/active` | GET | Active config |
| `/api/v1/merkle/configs` | POST | Create config |
| `/api/v1/merkle/configs/{id}` | GET | Get config by ID |
| `/api/v1/merkle/configs/activate` | PUT | Activate config |
| `/api/v1/merkle/trees` | GET | List trees |
| `/api/v1/merkle/trees/{id}` | GET | Get tree |
| `/api/v1/merkle/proofs` | GET | List proofs |
| `/api/v1/merkle/proofs/lookup` | GET | Lookup proof |
| `/api/v1/merkle/generate` | POST | Generate tree |
| `/api/v1/merkle/jobs/{id}` | GET | Poll generation job |
| `/api/v1/merkle/jobs` | GET | List jobs |
| `/api/v1/merkle/sync` | GET | Determine onchain sync action |
| `/api/v1/merkle/publish` | POST | Publish active proof artifact |
| `/api/v1/merkle/publish/jobs/{id}` | GET | Poll publish job |
| `/api/v1/merkle/published/{chain_id}/{vault}` | GET | Public published tree metadata |
## Registry
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/registry/yield-sources` | GET | Browse yield sources for operator selection |
| `/api/v1/registry/oracles` | GET | Browse oracles for operator selection |
| `/api/v1/registry/hooks` | GET | Browse hooks for merkle config authoring |
| `/api/v1/registry/token-assets` | GET | Browse token metadata |
| `/api/v1/registry/token-assets/{chain_id}/{address}` | GET | Read token metadata |
Yield source registry entries include active/inactive state and can carry external provider metadata. Registry create, update, delete, and sync endpoints are registry-maintainer surfaces, not normal third-party operator flows.
## Yield Source Whitelist
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/yield-sources/prepare-add` | POST | Prepare add transaction |
| `/api/v1/yield-sources/prepare-remove` | POST | Prepare remove transaction |
| `/api/v1/yield-sources/prepare-update-oracle` | POST | Prepare oracle update transaction |
| `/api/v1/yield-sources/prepare-bulk-add` | POST | Prepare bulk add transaction |
| `/api/v1/yield-sources/prepare-bulk-remove` | POST | Prepare bulk remove transaction |
| `/api/v1/yield-sources/prepare-bulk-update-oracle` | POST | Prepare bulk oracle update transaction |
## Users and Permissions
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/users` | GET | List users for a vault |
| `/api/v1/users/secondary-manager/prepare` | POST | Prepare add-secondary-manager transaction |
| `/api/v1/users/secondary-manager/remove/prepare` | POST | Prepare remove-secondary-manager transaction |
| `/api/v1/users/view-only` | POST | Add view-only user |
| `/api/v1/users/view-only/{wallet}` | DELETE | Remove view-only user |
| `/api/v1/users/{wallet}/display-name` | PATCH | Update display name |
## Session Keys
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/session-keys` | GET | List manager-visible session keys |
| `/api/v1/vaults/{chain_id}/{vault_address}/session-key` | GET | Read vault session key |
| `/api/v1/vaults/session-key/prepare` | POST | Prepare session-key grant |
| `/api/v1/vaults/session-key/revoke/prepare` | POST | Prepare session-key revoke |
OMS owns additional readiness and rotation endpoints for strategy automation.
## Notification Channels
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/notification-channels` | GET/POST | List or create channels |
| `/api/v1/vaults/{chain_id}/{vault_address}/notification-channels` | GET | List vault channels |
| `/api/v1/vaults/{chain_id}/{vault_address}/notification-channels/{channel_id}` | POST | Attach channel |
| `/api/v1/vaults/{chain_id}/{vault_address}/notification-channels/{channel_id}` | DELETE | Detach vault channel |
Providers include Slack, webhook, and websocket channels.
## Pause and Services
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/vaults/pause-status` | GET | Batch pause status |
| `/api/v1/vaults/emergency-pause/prepare` | POST | Prepare onchain pause |
| `/api/v1/vaults/emergency-unpause/prepare` | POST | Prepare onchain unpause |
| `/api/v1/vaults/{chain_id}/{vault}/services` | GET | List keeper services |
| `/api/v1/vaults/{chain_id}/{vault}/services/{name}/pause` | PUT | Pause service |
| `/api/v1/vaults/{chain_id}/{vault}/services/{name}/resume` | PUT | Resume service |
| `/api/v1/vaults/{chain_id}/{vault}/services/enabled` | PUT | Bulk enable/disable services |
| `/api/v1/vaults/{chain_id}/{vault}/services/{name}` | PUT | Update service config |
| `/api/v1/services/bulk` | PUT | Bulk service update |
## Analytics and Audit
Analytics endpoints live under:
```bash
/api/v1/vaults/{chain_id}/{address}/analytics/*
```
Audit endpoints:
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/audit/logs` | GET | Query audit trail |
| `/api/v1/audit/logs/{id}` | GET | Single audit row |
| `/api/v1/audit/export` | GET | Export audit rows |
---
# Strategy Engine API Reference
Source: https://docs.superform.xyz/operate/api/strategy
The Strategy Engine evaluates manager-defined rules and dispatches DeFiX intents to the OMS. It owns strategy lifecycle state, lane ordering, live shard state, intent history projections, and emergency locks.
**Base URL:** `https://strategy.superform.xyz`
**Auth:** `Authorization: Bearer `
`vault_id` uses `{chainId}:{vaultAddress}` format, for example `8453:0xdef...`. Erebor often uses separate `chain_id` and `vault_address` fields.
## Strategies
### List Strategies
```bash
GET /api/v1/strategies?vault_id={vault_id}&limit=50
```
Filters:
- `kind` — `USER_DEFINED` or `EMERGENCY_EXIT`.
- `state` — lifecycle state.
- `lane` — `DEPOSIT`, `WITHDRAWAL`, or `REBALANCE`.
- `target_address` — lowercased target yield source / route address.
- `limit`, `offset`.
Emergency strategies are separated by `kind=EMERGENCY_EXIT`; normal editor flows use `USER_DEFINED` by default.
### Create Strategy
```bash
POST /api/v1/strategies
```
Required fields include `id`, `vault_id`, `name`, `indicators`, `rules`, `action_config`, and `max_concurrent`.
Priority is not part of create. Use the reorder endpoint after creation.
### Update Strategy
```bash
PUT /api/v1/strategies/{strategy_id}
```
`version` is required for optimistic concurrency. A stale version returns `409 Conflict`.
Priority is not part of update. Use the reorder endpoint.
### Transition State
```bash
PATCH /api/v1/strategies/{strategy_id}/state
{
"target_state": "RUNNING",
"version": 12
}
```
Allowed target states include `IDLE`, `RUNNING`, `ARCHIVED`, and `ERROR` depending on current state and policy.
For emergency exits, arm requests may include `emergency_arm_reason`.
### Reorder Strategies
```bash
POST /api/v1/strategies/reorder
{
"vault_id": "8453:0xdef...",
"lane": "DEPOSIT",
"ordered_strategy_ids": ["strategy-a", "strategy-b"]
}
```
The backend atomically assigns priorities `1..N` inside the `(vault_id, lane)` partition.
Rules:
- Include every non-archived strategy in that lane exactly once.
- Do not include archived strategies.
- The lane must match the lane derived from each strategy action.
- Reorder is the only priority persistence path.
## Strategy Kinds
| Kind | Description |
|------|-------------|
| `USER_DEFINED` | Manager-authored strategy visible in normal canvas flows |
| `EMERGENCY_EXIT` | System-managed withdrawal strategy paired to one whitelisted yield source |
`EMERGENCY_EXIT` strategies are controlled from [Pause Operations](/operate/ui/pause). They are read-only in normal strategy editing flows.
## Vault Config and Shards
```bash
GET /api/v1/vaults/{vault_id}
PATCH /api/v1/vaults/{vault_id}
GET /api/v1/engine/status
GET /api/v1/engine/shards/{vault_id}/state
```
Vault state includes kill-switch / rate-limit settings. Shard state includes live indicator values, active strategy state, yield-source allocations, and active `emergency_locks`.
An emergency lock has:
```json
{
"strategy_id": "emergency-8453:0xdef-0xsource...",
"target_address": "0xsource...",
"started_at": "2026-05-09T14:21:00Z",
"reason": "Oracle anomaly"
}
```
## Intent History
The Strategy Engine maintains an operator-facing projection of OMS intent execution.
### Search Intents
```bash
GET /api/v1/intents
```
Common filters:
- `vault_id`
- `strategy_id`
- `action`
- `state`
- `chain_id`
- `tx_hash`
- `token_in`, `token_out`
- created/completed time ranges
- amount-in ranges
- `error_code`
- free-text search across `intent_id`, `execution_id`, `error_message`, and fill transaction hash
Cursor pagination is preferred for large histories. Default sort is newest first.
### Intent Facets
```bash
GET /api/v1/intents/facets
```
Returns counts by state, action, chain, vault, and strategy for filter UIs.
### Events and Fills
```bash
GET /api/v1/intents/{intent_id}/events
GET /api/v1/intents/{intent_id}/fills
GET /api/v1/fills
```
Use events for lifecycle reconstruction and fills for mined transaction evidence.
Intent rows may include nullable fields while OMS execution is still in flight. For example, `completed_at`, `terminal_at`, `gas_used_total`, and fill data may be `null` until execution events and receipts arrive.
## Simulations
Simulation endpoints let operators test strategy behavior before live execution. They do not publish OMS intents or submit onchain transactions.
```bash
POST /api/v1/simulations/backtest
POST /api/v1/simulations/ahead
GET /api/v1/simulations/saved
GET /api/v1/simulations/saved/{simulation_id}
POST /api/v1/simulations/saved/lookup
POST /api/v1/simulations/saved
DELETE /api/v1/simulations/saved/{simulation_id}
```
Use backtests for historical replay and forward simulations for current-state what-if analysis. See [Simulation](/operate/ui/simulation) for the operator flow.
## Important Schemas
### StrategyResponse
Important fields:
- `id`
- `vault_id`
- `name`
- `version`
- `state`
- `kind`
- `indicators`
- `rules`
- `action_config`
- `priority`
- `cooldown_ms`
- `max_concurrent`
### IntentLogEntry
Important fields:
- `intent_id`
- `execution_id`
- `strategy_id`
- `vault_id`
- `action`
- `state`
- `source`
- `fills_count`
- `last_event_seq`
- lifecycle timestamps such as `acked_at`, `executing_at`, `completed_at`, `terminal_at`
- `error_code`, `error_message`
- `total_amount_in`, `total_amount_out`
- `gas_used_total`
### IntentFillEntry
Important fields:
- `fill_id`
- `intent_id`
- `execution_id`
- `chain_id`
- `tx_hash`
- `amount_in`, `amount_out`
- `token_in`, `token_out`
- `gas_used`
- `fill_time`
- `venue`
## Endpoint Inventory
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/strategies` | GET | List strategies |
| `/api/v1/strategies` | POST | Create strategy |
| `/api/v1/strategies/{id}` | GET | Get strategy |
| `/api/v1/strategies/{id}` | PUT | Update strategy |
| `/api/v1/strategies/{id}` | DELETE | Archive strategy |
| `/api/v1/strategies/{id}/state` | PATCH | Transition state |
| `/api/v1/strategies/reorder` | POST | Persist lane order |
| `/api/v1/intents` | GET | Search intent projections |
| `/api/v1/intents/facets` | GET | Intent facets |
| `/api/v1/intents/{intent_id}/events` | GET | OMS event log |
| `/api/v1/intents/{intent_id}/fills` | GET | Fills for one intent |
| `/api/v1/fills` | GET | Search fills |
| `/api/v1/simulations/backtest` | POST | Run a historical strategy simulation |
| `/api/v1/simulations/ahead` | POST | Run a forward-looking simulation |
| `/api/v1/simulations/saved` | GET/POST | List or save simulations |
| `/api/v1/simulations/saved/{simulation_id}` | GET/DELETE | Read or delete a saved simulation |
| `/api/v1/simulations/saved/lookup` | POST | Find an existing saved simulation for the same inputs |
| `/api/v1/vaults/{vault_id}` | GET/PATCH | Vault engine config and emergency locks |
| `/api/v1/engine/status` | GET | Engine status |
| `/api/v1/engine/shards/{vault_id}/state` | GET | Live shard state |
---
# OMS API — DeFiX Intents & Session Keys
Source: https://docs.superform.xyz/operate/api/oms
The OMS implements the DeFiX intent execution path. The Strategy Engine publishes intents; OMS validates authorization, builds execution, submits hook transactions, and emits lifecycle events / fills back to operators.
**Base URL:** `https://oms.superform.xyz`
**Auth:** `Authorization: Bearer `
In normal dashboard operations, managers do not submit intents directly. They configure strategies and monitor the resulting intent history.
## Intent Lifecycle
OMS intent state is explicit and event-driven:
```text
RECEIVED → ACKED → STAGED → PLANNED → EXECUTING → COMPLETED
↘ ABORTED
↘ EXPIRED
↘ FAILED
↘ CANCELED
```
State values:
| State | Meaning |
|-------|---------|
| `RECEIVED` | OMS received the intent |
| `ACKED` | Intent accepted for processing |
| `STAGED` | Intent staged for planning/execution |
| `PLANNED` | Route or execution plan built |
| `EXECUTING` | Transaction execution in progress |
| `COMPLETED` | Execution completed |
| `ABORTED` | OMS aborted before success |
| `EXPIRED` | Intent expired before execution |
| `FAILED` | Execution failed |
| `CANCELED` | Intent cancelled before terminal execution |
Terminal states cannot be modified.
## Submit Intent
```bash
POST /defix/intents
```
Typical requests include:
- `vault_id`
- `strategy_id`
- `action`
- `objective`
- `action_config`
- `auth`
- merkle proof / root data
- account mode (`OMS`, `EIP4337`, `EIP4337_COMPANION`, or `EIP7702`)
OMS validates the intent and authorization before execution.
## Get Intent
```bash
GET /defix/intents/{intent_id}
```
Returns the current OMS state, latest sequence number, timestamps, and completion timestamp when available.
Fields such as `completed_at` may be `null` before terminal execution.
## Cancel Intent
```bash
POST /defix/intents/{intent_id}/cancel
```
Cancellation is only valid before the intent reaches a terminal state.
## Intent Events
```bash
GET /defix/intents/{intent_id}/events
```
Events are the source of truth for lifecycle reconstruction. Each event has:
- `event_id`
- `intent_id`
- `execution_id`
- `message_type`
- `seq_num`
- event-specific `payload`
- `created_at`
The Strategy Engine consumes these events and projects them into operator-friendly intent history.
## Fills and Mined Transactions
Fills represent confirmed onchain execution evidence. In the operator UI, fills may include:
- `tx_hash`
- `chain_id`
- `amount_in`
- `amount_out`
- `token_in`
- `token_out`
- `venue`
- `gas_used`
- mined / fill timestamp
Pre-receipt rows can have `null` gas or missing fill fields until the transaction is mined and backfilled.
## Hook Transactions and Account Modes
OMS supports native hook transaction execution and smart-account/user-operation modes. Account mode controls how execution is signed and submitted:
| Mode | Meaning |
|------|---------|
| `OMS` | OMS-managed delegated signer path |
| `EIP4337` | ERC-4337 user operation path |
| `EIP4337_COMPANION` | Companion 4337 path |
| `EIP7702` | EIP-7702 account path |
The hook transaction adapter handles execution against SuperVault hooks once session-key and merkle authorization are valid.
## Session Keys
Strategy automation requires a valid session key for the vault.
### Readiness
```bash
GET /defix/vaults/{vault_id}/session-key/readiness
```
Returns whether the vault has a valid session key and whether a rotation is pending.
### Grant
```bash
GET /defix/vaults/{vault_id}/session-key/grant
```
Returns calldata to grant a session key. The manager signs and submits the transaction.
### Rotate / Renew
```bash
GET /defix/vaults/{vault_id}/session-key/rotation
POST /defix/vaults/{vault_id}/session-key/rotation/complete
POST /defix/vaults/{vault_id}/session-key/rotation/cancel
```
Complete rotation after the grant/revoke transactions have mined.
### Revoke
```bash
GET /defix/vaults/{vault_id}/session-key/revoke
```
Returns calldata to revoke the current session key.
Session-key endpoints return transaction data for wallet signing. They do not bypass onchain permission checks.
## Endpoint Inventory
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/defix/intents` | POST | Submit intent |
| `/defix/intents/{intent_id}` | GET | Current intent state |
| `/defix/intents/{intent_id}/cancel` | POST | Cancel non-terminal intent |
| `/defix/intents/{intent_id}/events` | GET | Intent event log |
| `/defix/vaults/{vault_id}/session-key/readiness` | GET | Session-key readiness |
| `/defix/vaults/{vault_id}/session-key/grant` | GET | Prepare grant calldata |
| `/defix/vaults/{vault_id}/session-key/rotation` | GET | Prepare rotation calldata |
| `/defix/vaults/{vault_id}/session-key/rotation/complete` | POST | Complete rotation |
| `/defix/vaults/{vault_id}/session-key/rotation/cancel` | POST | Cancel pending rotation |
| `/defix/vaults/{vault_id}/session-key/revoke` | GET | Prepare revoke calldata |
---
# Security
Source: https://docs.superform.xyz/security
Superform has been reviewed by multiple independent security firms across both core and periphery contracts. The protocol enforces security at multiple layers: onchain Merkle verification for keeper operations, timelocked governance updates, circuit-breaker PPS validation, role-based access control, and continuous invariant monitoring.
## Audits
### v2-core
| Auditor | Date | Report |
|---------|------|--------|
| Orion Security | August 2025 | [Report](https://github.com/superform-xyz/v2-core/blob/dev/audits/2025.08.22-orion.pdf) |
| Cantina Competition | June 2025 | [Report](https://github.com/superform-xyz/v2-core/blob/dev/audits/2025.06.02-cantina-competition.pdf) |
| Node Security | May 2025 | [Report](https://github.com/superform-xyz/v2-core/blob/dev/audits/2025.05.05-nodesecurity.pdf) |
| Cantina Code | April 2025 | [Report](https://github.com/superform-xyz/v2-core/blob/dev/audits/2025.04.19-cantinacode.pdf) |
| Sujith Somraaj | March 2025 | [Report](https://github.com/superform-xyz/v2-core/blob/dev/audits/2025.03.24-sujithsomraaj.pdf) |
### v2-periphery
| Auditor | Date | Report |
|---------|------|--------|
| Octane Security | November 2025 | [Report](https://github.com/superform-xyz/v2-periphery/blob/dev/audits/2025.11.30-octane-security.pdf) |
| 0xMacro | November 2025 | [Report](https://github.com/superform-xyz/v2-periphery/blob/dev/audits/2025.11.27-0xMacro.pdf) |
| GetRecon | November 2025 | [Report](https://github.com/superform-xyz/v2-periphery/blob/dev/audits/2025.11.07-GetRecon.pdf) |
| Cantina Code | June 2025 | [Report](https://github.com/superform-xyz/v2-periphery/blob/dev/audits/2025.06.30-cantinacode.pdf) |
All critical properties and invariants are tested through the [GetRecon](https://getrecon.xyz/) invariant suite using both Echidna and Medusa across >100 million runs, and continuously monitored through [Tenderly](https://tenderly.co/monitoring).
## Security Model
### Dual Merkle Hook Validation
All keeper operations are gated by a dual Merkle tree system. A hook can only execute if the exact combination of hook address + parameters matches a leaf in one of two onchain roots:
| Root | Controlled By | Timelock | Scope |
|------|---------------|----------|-------|
| **Global hooks root** | SuperGovernor (governance) | Default 15 minutes | Protocol-wide hooks without beneficiary-specific arguments |
| **Strategy hooks root** | Main manager proposal via SuperVaultAggregator | Default 15 minutes | Vault-specific hooks including beneficiary-specific arguments |
Both hook-root flows pass through the aggregator's configurable hook-root timelock. Protocol governance can update that delay.
### PPS Oracle Security
Price-per-share updates pass through multiple validation layers before acceptance:
1. **ECDSAPPSOracle** validates EIP-712 signatures, quorum (≥ 2/3 weighted stake), and nonce binding
2. **SuperVaultAggregator** checks future timestamps, pause state, and staleness
3. **Strategy-level checks** enforce monotonicity, rate limiting, deviation bounds, dispersion thresholds, and upkeep balance
Strategies are automatically paused when:
- PPS dispersion exceeds threshold (high standard deviation among validator submissions)
- PPS deviation is too large (absolute change from current onchain value)
- Validator participation rate falls below minimum
- Updates are stale beyond the configured window
### Access Controls
| Role | Permission |
|------|-----------|
| **SuperGovernor** | Protocol governance with strategy-level overrides; updates global hook root |
| **Primary Manager** | Highest-authority vault operator for hook-root proposals, fee proposals, and deposit/redemption processing, subject to protocol, timelock, Merkle, PPS, and accounting constraints |
| **Secondary Managers** | Delegated day-to-day operations through role-authorized actions and pre-authorized Merkle leaves |
| **Guardians** | Threat monitoring network with emergency intervention powers such as pause controls |
| **Validators** | Validators that attest to PPS updates and must satisfy signature and quorum checks before updates are accepted |
### Timelock Protections
| Change Type | Timelock |
|-------------|----------|
| Hook-root updates (global and strategy) | Default 15 minutes, configurable by governance |
| Post-unpause skim | 12 hours |
| Upkeep withdrawal | 24 hours |
| Min update interval changes | 3 days |
| Primary strategist updates | 7 days |
| Fee configuration changes | 7 days |
| PPS expiration threshold updates | 7 days |
### Emergency Controls
**Strategy Pausing** — Both primary and secondary managers can pause strategies immediately. Unpausing marks PPS as stale until a fresh oracle update is posted.
**Banned Hook Leaves** — Individual Merkle leaves can be banned without regenerating the entire tree. This provides an immediate block on specific hook + parameter combinations.
**Guardian Controls** — Guardians retain emergency intervention powers such as pause controls, allowing rapid response to detected threats.
**Emergency Withdrawals** — Managers can perform emergency withdrawals through authorized hooks as de-risking paths back through strategy/vault accounting. Expanding that authorization still requires a hook-root proposal and execution flow before new leaves become active.
---
# Deployment Addresses
Source: https://docs.superform.xyz/deployment-addresses
Superform Core v2 contracts are deployed at deterministic addresses across Ethereum, Base, Optimism, Arbitrum, BNB, Polygon, Avalanche, Unichain, and HyperEVM.
## Core Contracts
### Infrastructure
| Contract | Address |
|----------|---------|
| SuperExecutor | `0x9cC8EDCC41154aaFC74D261aD3D87140D21F6281` |
| SuperDestinationExecutor | `0x6ac58e854798D4aae5989B18ad5a1C0fF17817EF` |
| SuperValidator | `0xB46b4773C5F53FF941533F5dfEFFD0713f5f9f8E` |
| SuperDestinationValidator | `0xADEFF5A0684392C4c273a9C638d1dB8c5dfd0098` |
| SuperBundler | `0x1101eec94dd79bee1b5a77b96c15ac24a4691e2e` |
| SuperSenderCreator | `0xBC6FB94D2f10A3B4349F592FFA80C4B7C97C1799` |
| SuperLedger | `0x04916bB42564CdED96E10F55C059d65E4FCb1Be6` |
| SuperLedgerConfiguration | `0x2e2D71289CBA19f831856f85DEC7f194B0165e69` |
| FlatFeeLedger | `0xAb56d09Ad9975116fCeb14970F2fFb3bB0ad683E` |
| Nexus | `0xA3AA31f8d4Da6005aAfB0d61e5012a64d15F5B3A` |
| NexusAccountFactory | `0x4153Db38136E74a88A77b51a955A88823820C050` |
| NexusBootstrap | `0x5eBeb4d51723bA345080D81bBF178D93E84bC9BE` |
| NexusProxy | `0x8a3A6698C3D142b9dAD80F114947d46671A5290E` |
| SuperNativePaymaster | `0x2288C49689c2CceD5C5bdd74Ac3b775E61a7A532` |
| SuperSponsorshipPaymaster | `0x8C71Eb1817a2707E8e40aC978B1993b98F1366aa` |
### Bridge Adapters
| Contract | Address |
|----------|---------|
| AcrossV3Adapter | `0x4dC34c4Eb23973F3551526C2AFE8ffb7f70F0fD7` |
| DebridgeAdapter | `0x5bE003c2cD2DaCD4Cd23488DB7E74568475a36d8` |
### Hooks
Hook addresses may vary across chains. Always verify hook addresses against the onchain registry before integrating directly.
| Contract | Address |
|----------|---------|
| AcrossSendFundsAndExecuteOnDstHook | `0x39962bE24192d0d6B6e3a19f332e3c825604d16A` |
| ApproveAndAcrossSendFundsAndExecuteOnDstHook | `0x72422aB917e4a698369767F7AcE667a769E0F3f2` |
| ApproveAndDeposit4626VaultHook | `0xF37535D96712FBaEf6D868e721E7b987ad1E6A86` |
| ApproveAndDeposit5115VaultHook | `0x44c7a40f05771FdAEAee61f36902D95cbf593988` |
| ApproveAndRequestDeposit7540VaultHook | `0x840B2b0553683dE46c5e6382D1a405f44773b43F` |
| ApproveAndSwapKyberSwapHook | `0xdC9D10d9710DBf82924a3F7733293457Ad12D37D` |
| ApproveAndSwapOdosV2Hook | `0x067696e1EfBD25cAfD3B55648ED253C20A7d9671` |
| ApproveERC20Hook | `0x8b789980dc6cC7d88E30C442D704646ff7F6d306` |
| BatchTransferFromHook | `0x816d5de8835FB7A003896f486fCce46a6DEBB00A` |
| BatchTransferHook | `0x852c6E00A7eC7590318DEAaD03030d4ddD74C93a` |
| CancelDepositRequest7540Hook | `0x0BBA42ddaa6ef6CCd228BD6270565F87154E921A` |
| CancelRedeemRequest7540Hook | `0x542601AfAEeB2E5dFc7d1F2fEEF5911285f0c2c0` |
| CircleGatewayAddDelegateHook | `0xa7aE1263fd7D6017770147393CE130f16E1fE2cC` |
| CircleGatewayMinterHook | `0x659b720a5E8E08D2c379165D17bA5F74dd104824` |
| CircleGatewayRemoveDelegateHook | `0x00FbC4e3608A26E0d05905759C2A6188fDa0e2Cd` |
| CircleGatewayWalletHook | `0x6383d09cF761FeAa4108B65130793c7eDA356dB5` |
| ClaimCancelDepositRequest7540Hook | `0xdf958A047D90b202A7097b5f9B67Bb8CB5285858` |
| ClaimCancelRedeemRequest7540Hook | `0x0668f9a638f34928f0bD91588E7B157F0699D594` |
| DeBridgeCancelOrderHook | `0xc5DbbBe2D8B9ff884a7ed33f1352021CD2b482C9` |
| DeBridgeSendOrderAndExecuteOnDstHook | `0x162225095A384787a257bced9b8893b29C8f1795` |
| Deposit4626VaultHook | `0xa067037B29431C1ff23dEB9b10CC8a1669B0698E` |
| Deposit5115VaultHook | `0x32209A2302865784bC1Dc0bd3C55D0A6eB205851` |
| Deposit7540VaultHook | `0x0aB1b12E090775fA67DF6e1b44DFAEe676C1DC84` |
| EthenaCooldownSharesHook | `0x1bD7698cc3E3f4cCF5D6CBC74a611bdDEaB18aeF` |
| EthenaUnstakeHook | `0xaEBeEc6548B727fd4f3464B19D99f4676d7e7796` |
| MarkRootAsUsedHook | `0xE61774Aa87a05fB1B5665158F2b5E0E10C71B5e2` |
| MerklClaimRewardHook | `0x6C12D4453Ed2278B37eCd169F4B8693537b228Df` |
| MetaMorphoReallocateHook | `0x0709C6211aDaf33D218275FdaceEA7d8c872Ab79` |
| MorphoBorrowHook | `0x860EC9cFA60C0FD3c0b5940C2C3376cfc57DaaF1` |
| MorphoLendHook | `0xFdA77d57d22c7EB62ef768212BA8B08f82c9b5E3` |
| MorphoRepayAndWithdrawHook | `0x65f289Cdf87b54095401A17af3C3Eb626B8e5e01` |
| MorphoRepayHook | `0xF78c9aB2AD1104a0597a1E32e4b2F5e074C5c4a2` |
| MorphoSupplyAndBorrowHook | `0xAf77F98fd73524253A19086105c6C3944039053d` |
| MorphoSupplyHook | `0xa8d51a5E4D774D5FD65a915aF732AC23c388D1dc` |
| MorphoWithdrawHook | `0x2E80aace37Fb7A9C858B9398f23Dd3e771D52B22` |
| OfframpTokensHook | `0xFbbD9a7026E29e889d28882606660fc5Be0BeA73` |
| PendleRouterRedeemHook | `0xAae2DB58E2f426b910f518cCbB627545aEdaff2F` |
| PendleRouterSwapHook | `0x02A0A95C379220E9759960A8Ee923cBbC2d305cd` |
| PendleUnifiedHook | `0x433f9A343e4132a294e02D4a09Da4b575EebA9aD` |
| RecordPurchasePendlePTAmortizedOracleHook | `0x771D4fF615F87eA00488a2dbcb70DF98BDA03FA3` |
| RecordPurchasePendlePTAmortizedOracleHookV2 | `0xA0E61eb90817E28aBbb5a40045921B69bb784431` |
| RecordRedemptionPendlePTAmortizedOracleHook | `0xb68a34AF34E64a8b3bB72983088ACeB2fAE326Fc` |
| RecordRedemptionPendlePTAmortizedOracleHookV2 | `0x2A4F700923324B14bd546630Fe87B1ee08C89634` |
| Redeem4626VaultHook | `0x5c3edf3F7c43828Bb72a668e2B29f9e2D9Af5A69` |
| Redeem5115VaultHook | `0x6aB1fD107825F9bB3E079d23508A07486b44e6F5` |
| Redeem7540VaultHook | `0xE165FBBc89a60756F57Cf0E34c04c35Cc1BbA79D` |
| RequestDeposit7540VaultHook | `0xBE7738b26992a322D53edEb9a39331Bf11b60097` |
| RequestRedeem7540VaultHook | `0x9c21c130aCF3EADd781AE79d75FF5fC4Bd216797` |
| SetOperator7540Hook | `0x86f9DcE0a1A83C501Ba95a1aB1088d67978636A8` |
| SetSlippageHook | `0x6551d0140FFdB28920E5e84DC3DA31f4bfe4364E` |
| Swap1InchHook | `0x1303d5f3e3D9e4a81945cB0C2e309E1940d2425C` |
| SwapKyberSwapHook | `0x828d8a9c015CEf90b373a071500AE463eD204dF9` |
| SwapOdosV2Hook | `0xf09106eAA87a5d81DAcFC91F6F564131c57495bE` |
| TransferERC20Hook | `0x6031c3953BC12D9Af4651B7ed517190A31a67ca4` |
| TransferHook | `0x0d54e1b4060bBD598eE6ec8F7A587fF1789164E9` |
### Yield Source Oracles
| Contract | Address |
|----------|---------|
| ERC4626YieldSourceOracle | `0xd12A40B2aBD166e17f18854f57ccd202091D9fB8` |
| ERC5115YieldSourceOracle | `0xEC64fE4256e9e2B935F9acb13cF0B1cE06C6DD5C` |
| ERC7540YieldSourceOracle | `0x8963d668adCE629996cA0247885771B490612005` |
| FirelightYieldSourceOracle | `0x211E048350c5b61704245BDABfefe95a1239dfE7` |
| PendlePTAmortizedOracle | `0xD64089698f82cbCD91ba5e0422aDFa81D247eB62` |
| PendlePTAmortizedOracleV2 | `0x2185B40476510Ad27d17AF90889CE91BE9282A04` |
| PendlePTYieldSourceOracle | `0x98D40e5B9D0911F15278223D58fdDCB5cB4799A6` |
| SpectraPTYieldSourceOracle | `0x30Ecd1150B3d198d75c51DBb41Bb7711Bd1E3260` |
| StakingYieldSourceOracle | `0x6A685CAD15b7BB46094497243DcF94aD6557394c` |
| SuperYieldSourceOracle | `0x98F0682ef39dE9cd6028D91090Be6EdAE129f52D` |
| SuperVaultYieldSourceOracle | `0xeEbb42210D8a8B165dCF154b325C588EE8dF149A` |
| YoYieldSourceOracle | `0x125d43f5F35c032a45aaD41EBE344d5c65D626D4` |
## SuperVault Contracts
Core SuperVault infrastructure contracts. These addresses are the same on both Ethereum and Base.
| Contract | Address |
|----------|---------|
| SuperGovernor | [`0xB5396ef2bF8CA360cEB4166b77AFb2bed20e74d4`](https://etherscan.io/address/0xB5396ef2bF8CA360cEB4166b77AFb2bed20e74d4) |
| SuperVaultAggregator | [`0x10AC0b33e1C4501CF3ec1cB1AE51ebfdbd2d4698`](https://etherscan.io/address/0x10AC0b33e1C4501CF3ec1cB1AE51ebfdbd2d4698) |
| ECDSAPPSOracle | [`0x366d88F03B8EF34eb49F32a927ff6e1609F694F2`](https://etherscan.io/address/0x366d88F03B8EF34eb49F32a927ff6e1609F694F2) |
| SuperBank | [`0x6fCc6a6A825FC14e6e56Fd14978FC6B97ACB5d15`](https://etherscan.io/address/0x6fCc6a6A825FC14e6e56Fd14978FC6B97ACB5d15) |
| SuperOracle | [`0x8943128DbAb4279D561654dEED2930Bb975AA070`](https://etherscan.io/address/0x8943128DbAb4279D561654dEED2930Bb975AA070) |
Individual vault, strategy, and escrow addresses vary per deployment. To look up a specific SuperVault's contracts, visit [SuperformOS](https://os.superform.xyz).
## $UP Token
| Chain | Address |
|-------|---------|
| Ethereum | [`0x1d926bbe67425c9f507b9a0e8030eedc7880bf33`](https://etherscan.io/token/0x1d926bbe67425c9f507b9a0e8030eedc7880bf33) |
| Base (LayerZero OFT) | [`0x5b2193fdc451c1f847be09ca9d13a4bf60f8c86b`](https://basescan.org/address/0x5b2193fdc451c1f847be09ca9d13a4bf60f8c86b) |
| HyperEVM (LayerZero OFT) | [`0x642fFC3496AcA19106BAB7A42F1F221a329654fe`](https://hyperevmscan.io/address/0x642fFC3496AcA19106BAB7A42F1F221a329654fe) |
| BNB Chain (LayerZero OFT) | [`0x5b2193fDc451C1f847bE09CA9d13A4Bf60f8c86B`](https://bscscan.com/address/0x5b2193fDc451C1f847bE09CA9d13A4Bf60f8c86B) |
| Robinhood Chain (LayerZero OFT) | `0xA85abEf37c7e812ACA761b2BEC62fFF7f3728F1E` |
| Flare (LayerZero OFT) | [`0xe030A89fd2b7f858c8aA47725679CA25D467dFD1`](https://flare-explorer.flare.network/address/0xe030A89fd2b7f858c8aA47725679CA25D467dFD1) |
| sUP (Base staking vault) | [`0x2c71f70e2Ec720AE061Ae7E0316fC9654d94f417`](https://basescan.org/address/0x2c71f70e2Ec720AE061Ae7E0316fC9654d94f417) |
## SuperVault Instances (Base, chain ID 8453)
All addresses verified from `GET https://persephone.superform.xyz/v1/supervaults?chain_id=8453`.
| Vault | Symbol | Underlying | Vault address | Underlying address |
|---|---|---|---|---|
| NVDA SuperVault | superNVDA | NVDAc | `0xc441a2cc3a528b6312740448a70cc4d40f4d7bfc` | `0xb20000000000000000000078ee7ce2fe4908108c` |
| TSLA SuperVault | superTSLA | TSLAc | `0xaf0edf09ec7f9357292f6bc6445a09aa84a31fa7` | `0xb2000000000000000000001e800a7f5189430cd0` |
| SPCX SuperVault | superSPCX | SPCXc | `0x02b12a394f0a98b80e53d85f6831041932f1b621` | `0xb2000000000000000000007b9fcbd005511acbd5` |
| AAPL SuperVault | superAAPL | AAPLc | `0x301a1ceb684d62833101b373ab6c395349b24033` | `0xb200000000000000000000c2e324d24d7eecd1fb` |
| GOOGL SuperVault | superGOOGL | GOOGLc | `0x363600ac7173e8d91bcfee68d711bb93e992c110` | `0xb2000000000000000000002d0ba3164cc74f58b7` |
| META SuperVault | superMETA | METAc | `0xc63374bf46d7a9654a96b14a3dc363c92b26d5a3` | `0xb2000000000000000000008bc8786b856e61707c` |
| AMZN SuperVault | superAMZN | AMZNc | `0x4855b2756910b7525d5b466292c394669ee74e7e` | `0xb200000000000000000000d9192b6b456483c2e8` |
| MSFT SuperVault | superMSFT | MSFTc | `0x7778192c7e2c22a27c63d63485699888148d5919` | `0xb200000000000000000000ab99cfa739e253872b` |
| Stocks USDC SuperVault | superStocksUSDC | USDC | `0x557202e4dea65af6a4460f04044732ce482c8627` | `0x833589fcd6edb6e08f4c7c32d4f71b54bda02913` |
| Flagship USDC SuperVault | superUSDC | USDC | `0x11820afe50ea96851ee2bdbae329d97771e41ec6` | `0x833589fcd6edb6e08f4c7c32d4f71b54bda02913` |
| Flagship WETH SuperVault | superWETH | WETH | `0x0e70c10fa06931f7b878653a15aecc86145c1af7` | `0x4200000000000000000000000000000000000006` |
| Flagship CBBTC SuperVault | superCBBTC | cbBTC | `0xfc8a6526ffcd8248b8d0f8dac8037dbe438924ce` | `0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf` |
| Staked UP Vault | sUP | UP | `0x2c71f70e2ec720ae061ae7e0316fc9654d94f417` | `0x5b2193fdc451c1f847be09ca9d13a4bf60f8c86b` |
---