Skip to main content
Single-depositor vaults are the simplest Aera V3 vault type: one owner deposits assets, and the guardian executes strategy operations on their behalf. For background on how the guardian role works at the protocol level, see the Guardian Model.
Before starting, make sure you have:
  • A wallet with guardian permissions on the target vault
  • viem 2.x installed (npm install viem) or Foundry for the cast CLI
  • Familiarity with the Aera V3 Guardian Model

Setup

Configure your environment for interacting with the vault contract. The guardian needs both a signing wallet and a way to read on-chain state.
import {
  createPublicClient,
  createWalletClient,
  http,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'

const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY')

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(process.env.RPC_URL_ETHEREUM!),
})

const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http(process.env.RPC_URL_ETHEREUM!),
})

// Vault and fee contract addresses — see Contract Reference for deployed addresses
// https://docs.gauntlet.xyz/contract-reference/addresses
const VAULT_ADDRESS = '0x...' as const
const FEE_CALCULATOR_ADDRESS = '0x...' as const

Strategy Execution

Strategy execution is the guardian’s core responsibility — submitting operations that allocate capital, rebalance positions, and interact with DeFi protocols on behalf of the vault. Operations are submitted in batches and validated against the guardian’s Merkle tree before execution.

Submitting Operations

The submit function on BaseVault accepts an array of operations to execute atomically. Each operation specifies a target contract, calldata, and Merkle proof elements. For the full Operation struct and validation details, see the BaseVault Contract Reference.
// Minimal ABI fragment for submit — see BaseVault Contract Reference for full ABI
const baseVaultAbi = [
  {
    name: 'submit',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{
      name: 'operations',
      type: 'tuple[]',
      components: [
        { name: 'target', type: 'address' },
        { name: 'data', type: 'bytes' },
        { name: 'value', type: 'uint256' },
        { name: 'proof', type: 'bytes32[]' },
        { name: 'preHook', type: 'address' },
        { name: 'preHookData', type: 'bytes' },
        { name: 'postHook', type: 'address' },
        { name: 'postHookData', type: 'bytes' },
      ],
    }],
    outputs: [],
  },
] as const

// Example: single operation targeting a DeFi protocol
const operations = [
  {
    target: '0x...' as `0x${string}`,   // Target protocol contract
    data: '0x...' as `0x${string}`,     // Encoded function call
    value: 0n,
    proof: [] as `0x${string}`[],       // Merkle proof for this operation
    preHook: '0x0000000000000000000000000000000000000000' as `0x${string}`,
    preHookData: '0x' as `0x${string}`,
    postHook: '0x0000000000000000000000000000000000000000' as `0x${string}`,
    postHookData: '0x' as `0x${string}`,
  },
]

const { request } = await publicClient.simulateContract({
  address: VAULT_ADDRESS,
  abi: baseVaultAbi,
  functionName: 'submit',
  args: [operations],
  account,
})

const txHash = await walletClient.writeContract(request)
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 2 })

console.log('Operations submitted in block:', receipt.blockNumber)
Operations are validated against the guardian’s Merkle tree on-chain. If an operation’s proof is invalid or the target is not whitelisted, the entire batch reverts with Vault__InvalidProof. The vault owner sets the Merkle root via setGuardianRoot.

Reading Vault State

Monitor vault state between submissions to inform strategy decisions. The Gauntlet API provides pre-computed metrics, while on-chain reads give real-time contract state. Via API — For vault metrics including TVL, APY, and share price, use the Gauntlet API. See Displaying Data for complete API coverage and endpoint details. Via on-chain reads — Query the vault contract directly for current state:
const vaultReadAbi = [
  {
    name: 'guardian',
    type: 'function',
    stateMutability: 'view',
    inputs: [],
    outputs: [{ type: 'address' }],
  },
  {
    name: 'paused',
    type: 'function',
    stateMutability: 'view',
    inputs: [],
    outputs: [{ type: 'bool' }],
  },
  {
    name: 'owner',
    type: 'function',
    stateMutability: 'view',
    inputs: [],
    outputs: [{ type: 'address' }],
  },
] as const

const [guardian, isPaused, owner] = await Promise.all([
  publicClient.readContract({
    address: VAULT_ADDRESS,
    abi: vaultReadAbi,
    functionName: 'guardian',
  }),
  publicClient.readContract({
    address: VAULT_ADDRESS,
    abi: vaultReadAbi,
    functionName: 'paused',
  }),
  publicClient.readContract({
    address: VAULT_ADDRESS,
    abi: vaultReadAbi,
    functionName: 'owner',
  }),
])

console.log('Guardian:', guardian)
console.log('Paused:', isPaused)
console.log('Owner:', owner)

Rebalancing

Rebalancing is submitting a new batch of operations that adjusts the vault’s position allocations. A typical rebalance involves multiple chained operations — for example, withdrawing from one protocol and depositing into another in a single atomic submission.
// Example: two-step rebalance — withdraw from Protocol A, deposit into Protocol B
// Each operation needs its own Merkle proof from the guardian's tree

const rebalanceOps = [
  {
    target: '0x...' as `0x${string}`,   // Protocol A — withdraw
    data: '0x...' as `0x${string}`,     // Encoded withdraw call
    value: 0n,
    proof: [] as `0x${string}`[],
    preHook: '0x0000000000000000000000000000000000000000' as `0x${string}`,
    preHookData: '0x' as `0x${string}`,
    postHook: '0x0000000000000000000000000000000000000000' as `0x${string}`,
    postHookData: '0x' as `0x${string}`,
  },
  {
    target: '0x...' as `0x${string}`,   // Protocol B — deposit
    data: '0x...' as `0x${string}`,     // Encoded deposit call
    value: 0n,
    proof: [] as `0x${string}`[],
    preHook: '0x0000000000000000000000000000000000000000' as `0x${string}`,
    preHookData: '0x' as `0x${string}`,
    postHook: '0x0000000000000000000000000000000000000000' as `0x${string}`,
    postHookData: '0x' as `0x${string}`,
  },
]

const { request: rebalanceRequest } = await publicClient.simulateContract({
  address: VAULT_ADDRESS,
  abi: baseVaultAbi,
  functionName: 'submit',
  args: [rebalanceOps],
  account,
})

const rebalanceTx = await walletClient.writeContract(rebalanceRequest)
await publicClient.waitForTransactionReceipt({ hash: rebalanceTx, confirmations: 2 })

console.log('Rebalance submitted:', rebalanceTx)
Operations within a batch execute sequentially, so the withdrawal completes before the deposit begins. If any operation fails or its Merkle proof is invalid, the entire batch reverts atomically. For details on operation chaining and callbacks, see the Guardian Model.

Fee Reporting

Fee reporting ensures accurate fee accrual for the vault owner and fee recipient. The guardian periodically reports the vault’s current value, which the fee calculator uses to compute management and performance fees.

How Fees Work

Single-depositor vaults use the DelayedFeeCalculator for fee computation. Two fee types apply:
  • Management fee — A percentage of assets under management (AUM), accruing over time. The fee is proportional to the vault value and the time elapsed since the last report.
  • Performance fee — A percentage of gains above a high-water mark. Only accrues when the vault value exceeds its previous peak.
The DelayedFeeCalculator uses time-delayed accrual to prevent fee manipulation through short-term vault value changes. See the Periphery Contract Reference for full function signatures.

Reporting Fees

Report the current vault value to trigger fee calculation. This updates the fee calculator’s internal state and starts the delayed accrual window.
const feeCalculatorAbi = [
  {
    name: 'reportValue',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{ name: 'vaultValue', type: 'uint256' }],
    outputs: [],
  },
] as const

const feeVaultAbi = [
  {
    name: 'reportFees',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{ name: 'vaultValue', type: 'uint256' }],
    outputs: [],
  },
] as const

// Step 1: Report value to the fee calculator
const currentVaultValue = 1000000n * 10n ** 18n // Example: 1M tokens in 18 decimals

const { request: reportValueReq } = await publicClient.simulateContract({
  address: FEE_CALCULATOR_ADDRESS,
  abi: feeCalculatorAbi,
  functionName: 'reportValue',
  args: [currentVaultValue],
  account,
})
await walletClient.writeContract(reportValueReq)

// Step 2: Report fees to the vault
const { request: reportFeesReq } = await publicClient.simulateContract({
  address: VAULT_ADDRESS,
  abi: feeVaultAbi,
  functionName: 'reportFees',
  args: [currentVaultValue],
  account,
})
await walletClient.writeContract(reportFeesReq)

console.log('Fees reported for vault value:', currentVaultValue.toString())

Claiming Fees

The fee recipient claims accrued fees from the vault. Specify the token, amount, and recipient address.
const claimFeesAbi = [
  {
    name: 'claimFees',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'recipient', type: 'address' },
    ],
    outputs: [],
  },
] as const

const FEE_TOKEN = '0x...' as const       // Token to claim fees in
const FEE_RECIPIENT = '0x...' as const   // Address to receive fees
const claimAmount = 50000n * 10n ** 18n  // Amount to claim

const { request: claimReq } = await publicClient.simulateContract({
  address: VAULT_ADDRESS,
  abi: claimFeesAbi,
  functionName: 'claimFees',
  args: [FEE_TOKEN, claimAmount, FEE_RECIPIENT],
  account,
})

const claimTx = await walletClient.writeContract(claimReq)
await publicClient.waitForTransactionReceipt({ hash: claimTx, confirmations: 2 })

console.log('Fees claimed:', claimTx)

Monitoring Accrued Fees

Check how much in fees has accrued and is available for claiming.
const accruedFeesAbi = [
  {
    name: 'accruedFees',
    type: 'function',
    stateMutability: 'view',
    inputs: [],
    outputs: [{ type: 'uint256' }],
  },
] as const

const accrued = await publicClient.readContract({
  address: VAULT_ADDRESS,
  abi: accruedFeesAbi,
  functionName: 'accruedFees',
})

console.log('Accrued fees:', accrued.toString())

Emergency Procedures

Emergency procedures allow the guardian to halt vault operations immediately if a security concern arises. These are safety mechanisms — use them when you detect anomalous conditions, compromised keys, or unexpected protocol behavior.

Pausing the Vault

Calling pause immediately halts all guardian operations on the vault. Both the guardian and the vault owner can pause.
const pauseAbi = [
  {
    name: 'pause',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [],
    outputs: [],
  },
] as const

const { request: pauseReq } = await publicClient.simulateContract({
  address: VAULT_ADDRESS,
  abi: pauseAbi,
  functionName: 'pause',
  account,
})

const pauseTx = await walletClient.writeContract(pauseReq)
await publicClient.waitForTransactionReceipt({ hash: pauseTx, confirmations: 2 })

console.log('Vault paused:', pauseTx)
Only the vault owner can call unpause to resume operations. As a guardian, once you pause the vault, you cannot unpause it yourself. Coordinate with the vault owner before pausing in non-emergency situations.

Checking Pause Status

Verify whether the vault is currently paused before attempting operations.
const pausedAbi = [
  {
    name: 'paused',
    type: 'function',
    stateMutability: 'view',
    inputs: [],
    outputs: [{ type: 'bool' }],
  },
] as const

const isPaused = await publicClient.readContract({
  address: VAULT_ADDRESS,
  abi: pausedAbi,
  functionName: 'paused',
})

console.log('Vault paused:', isPaused)

Guardian Whitelist Check

Verify that the guardian is still authorized on the vault’s whitelist. Anyone can call this function — if the guardian fails the whitelist check, they are removed from the vault.
const whitelistAbi = [
  {
    name: 'checkGuardianWhitelist',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [],
    outputs: [],
  },
] as const

const { request: whitelistReq } = await publicClient.simulateContract({
  address: VAULT_ADDRESS,
  abi: whitelistAbi,
  functionName: 'checkGuardianWhitelist',
  account,
})

const whitelistTx = await walletClient.writeContract(whitelistReq)
await publicClient.waitForTransactionReceipt({ hash: whitelistTx, confirmations: 2 })

console.log('Guardian whitelist check passed:', whitelistTx)