# CONTEXT.md Source: https://docs.gauntlet.xyz/agents/context Copy this into your LLM context window for accurate, grounded answers about the Gauntlet SDK. Copy the block below into your system prompt or context window. It gives an LLM everything it needs to answer questions about the SDK, generate correct integration code, and avoid common mistakes. ````markdown theme={null} # Gauntlet SDK — CONTEXT.md ## Package - Name: `@gauntlet-xyz/sdk` - Install: `npm install @gauntlet-xyz/sdk` (peer dep: `viem >=2.0.0`) - Import paths: - `@gauntlet-xyz/sdk` — root: GauntletClient, AttributionMode, all functions, types, errors - `@gauntlet-xyz/sdk/evm` — EVM subpath: tx-building functions + types (preferred for tree-shaking) - `@gauntlet-xyz/sdk/api` — Gaia REST API client (`GauntletApi`) + data helpers, usable standalone - `@gauntlet-xyz/sdk/privy` — Privy wallet integration helpers --- ## GauntletClient Main entry point. Pass one instance to every SDK function. ```typescript import { GauntletClient, AttributionMode } from '@gauntlet-xyz/sdk' const client = new GauntletClient({ evmClients: { [chainId]: publicClient }, // viem PublicClient per chain wallet: walletClient, // viem WalletClient — SDK reads account address only, never signs apiKey: '', // optional — sent as x-api-key on client.api requests apiUrl: 'https://api.gauntlet.xyz', // optional — override Gaia API origin (relative path OK in browser) builderCode: 'your-code', // optional — MUST be issued by Gauntlet, not self-serve attributionMode: AttributionMode.PUBLIC, // optional — default PUBLIC }) client.api // typed Gaia REST API client — see "Gaia API" section ``` | Param | Type | Required | Notes | |-------|------|----------|-------| | `evmClients` | `Record` | For tx methods | chainId → viem PublicClient | | `wallet` | `WalletClient` | For tx methods | Only reads `wallet.account.address` | | `apiKey` | `string` | No | Gaia API key, sent as `x-api-key`. Anonymous access is rate-limited. | | `apiUrl` | `string` | No | Gaia API origin override. Defaults to `https://api.gauntlet.xyz`. In a browser it may be a relative path (e.g. a Next.js rewrite like `/gauntlet-api`). | | `builderCode` | `string` | No | Must be issued by Gauntlet. Without it, transactions are unattributed. | | `attributionMode` | `AttributionMode` | No | `PUBLIC` (default). `ENCODED`/`PRIVATE` throw `UnimplementedFeatureError`. | Other members: `client.manifest` (Promise of the bundled vault manifest), `client.setManifest(manifest)` (override it), `client.getPublicClient(chainId?)` (defaults to Base; throws `RpcNotConfiguredError`). --- ## EVM Functions ### getDepositTx(client, params) → Promise Builds ordered EVM transactions to deposit into a vault. Returns 1–2 steps: optional ERC-20 `approve` (only if allowance insufficient) + `deposit` or `requestDeposit`. ```typescript import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm' const steps = await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, // or any VaultId value or raw string amount: 1_000_000n, // token base units chainId: 8453, // optional; defaults to Base (8453) for multi-chain vaults assetSymbol: 'USDC', // optional; required for multi-asset vaults depositMode: 'async', // optional; 'async' | 'sync'. Availability read from live vault config. receiver: '0x...', // optional; defaults to wallet.account. Aera V1: MUST equal signer. slippageBps: 100, // optional; integer 0–10000. Default 100 (1%). solverTip: 0n, // optional; tip for async Aera provisioner requests. Default 0. maxPriceAge: 864_000n, // optional; max price age (seconds) for async Aera requests. Default 10 days. }) ``` Throws: `VaultNotFoundError`, `AccountRequiredError`, `UnsupportedDepositModeError`, `UnsupportedAssetError`, `InvalidSlippageBPSError`, `InvalidSolverTipError`, `RpcNotConfiguredError` --- ### getWithdrawTx(client, params) → Promise Builds ordered EVM transactions to withdraw from a vault. Exactly one of `shares`, `amount`, or `entireAmount` is required. ```typescript import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm' const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, entireAmount: true, // OR: shares: 500_000000000000000000n OR: amount: 1_000_000n chainId: 8453, // optional; defaults to Base assetSymbol: 'USDC', // optional; required for multi-asset vaults depositMode: 'async', // optional; 'async' | 'sync'. Aera: withdrawals blocked for 1 hour after a sync deposit. receiver: '0x...', // optional; receives the withdrawn assets (shares always burn from the signer). Defaults to wallet.account slippageBps: 100, // optional; integer 0–10000. Default 100 (1%). solverTip: 0n, // optional; default 0 maxPriceAge: 864_000n, // optional; default 10 days }) ``` Throws: `VaultNotFoundError`, `AccountRequiredError`, `UnsupportedDepositModeError`, `UnsupportedAssetError`, `InvalidSlippageBPSError`, `InvalidSolverTipError`, `InvalidWithdrawParamsError`, `RpcNotConfiguredError` --- ### getDepositReceiverApprovalTx(client, params) → Promise Aera V2 only. Builds the receiver-side approval transaction required before a depositor can make **sync** deposits to a separate receiver. The client's wallet account is the receiver and must sign this transaction. ```typescript import { getDepositReceiverApprovalTx } from '@gauntlet-xyz/sdk/evm' const step = await getDepositReceiverApprovalTx(client, { vaultId: 'gtusda', depositor: '0xDepositor', // the address that will deposit on the receiver's behalf approved: true, // optional; default true. false revokes. chainId: 8453, // optional }) ``` Throws: `VaultNotFoundError`, `AccountRequiredError`, `UnsupportedFeatureError` (on V1 vaults), `RpcNotConfiguredError` --- ### getVaults(client, filter?) → Promise Returns vaults from bundled manifest. No network request. ```typescript import { getVaults } from '@gauntlet-xyz/sdk/evm' const vaults = await getVaults(client, { chainId: 8453, protocol: 'aera' }) // filter is optional — omit to return all vaults ``` --- ### getUserCurrentBalance(client, params) → Promise Returns balance breakdown for a user across all deployments of a vault. Only works for Aera multi-depositor vaults. Makes on-chain RPC calls (scans ~3 days of provisioner events for pending requests). ```typescript import { getUserCurrentBalance } from '@gauntlet-xyz/sdk' const balances = await getUserCurrentBalance(client, { vaultId: 'gtusda', address: '0xUser', chainId: 8453, // optional; omit to return one entry per chain the vault is deployed on }) ``` Throws: `VaultNotFoundError`, `UnsupportedProtocolError`, `ChainMismatchError`, `RpcNotConfiguredError` --- ## Gaia API — client.api Typed client for the Gaia REST API (`api.gauntlet.xyz`): indexed vaults, user positions with PnL, activity logs, TVL, and token prices. Available as `client.api` on a configured `GauntletClient`, or standalone: ```typescript import { GauntletApi } from '@gauntlet-xyz/sdk/api' const api = new GauntletApi({ apiKey: process.env.GAUNTLET_API_KEY }) ``` ### Conventions - **Vault ids are CAIP-10-style**: `"{chainId}:{address}"` with a lowercase address (e.g. `"8453:0xabc…"`) — NOT manifest vault ids like `'gtusda'`. Convert with `apiVaultIdFromVaultId(client, 'gtusda')` / `vaultIdFromApiVaultId(client, apiId)`. - **Amounts are human-unit decimal strings** (e.g. `"1250.5"`), not base-unit bigints. Convert with `decimalToBigInt(value, decimals)` / `sharesToBigInt(value)` (shares are always 18 decimals — `SHARE_DECIMALS`). Conversion is exact: excess precision throws `DecimalPrecisionError` instead of rounding. - **Pagination is cursor-based**: pass `{ next: previousResponse.meta.next_cursor }`. Page size is capped at 1000 rows. - Response types are generated from the service's OpenAPI spec, so they cannot drift from the server models. ### Endpoints ```typescript api.health() // GET /health — liveness (version + uptime) api.chainSyncStatus() // GET /health/chains — per-chain indexer freshness api.vaults(page?) // GET /v1/vaults — all indexed vaults with live metrics (TVL, APY, unit price) api.vault(apiVaultId) // GET /v1/vaults/{vault_id} api.vaultDefinition(apiVaultId) // GET /v1/vaults/{vault_id}/definition — raw indexed definition api.vaultTimeseries(apiVaultId, window?) // GET /v1/vaults/{vault_id}/timeseries — TVL / unit-price / APY history api.positions(wallet, page?) // GET /v1/users/{wallet}/positions — all positions with PnL api.position(wallet, apiVaultId) // GET /v1/users/{wallet}/positions/{vault_id} — one position with PnL breakdown api.positionTimeseries(wallet, apiVaultId, window?) // value / cost-basis / PnL / ROI history api.activity(wallet, options?) // GET /v1/users/{wallet}/activity — one page of the immutable event log api.activityRows(wallet, options?) // async generator — auto-follows next_cursor across pages api.tvl({ mode? }) // GET /v1/tvl — aggregate Gauntlet TVL ('chain' | 'strategy') api.latestPrice({ address, chainId, at? }) // GET /v1/prices — latest (or point-in-time) USD token price api.priceTimeseries({ address, chainId, start, end, granularity?, limit? }) // USD price history ``` Options types: `PageOptions { next?, limit?, order? }`, `TimeWindowOptions extends PageOptions { start?, end?, granularity? }` (`granularity`: `'hour' | 'day' | 'week' | 'month'`; dates are ISO 8601 / RFC 3339), `ActivityOptions extends PageOptions { vaultId? }` (CAIP-10; omit for wallet-wide activity). Failed requests throw `GauntletApiError` with `.status`, `.path`, and optional `.code`. ### Activity flows (high-level helper) The raw activity log is an immutable row stream; Aera async lifecycles span multiple rows (`deposit_pending` → `deposit`/`deposit_refunded`) paired by `request_hash`. `getActivityFlows` fetches and stitches them into one flow per user action — the replacement for scanning vault event logs over RPC. ```typescript import { getActivityFlows, waitForRequestSettlement } from '@gauntlet-xyz/sdk/api' const flows = await getActivityFlows(client.api, wallet, { vaultId?, maxRows? }) // newest-first const open = flows.filter((f) => f.status === 'pending') // After submitting a requestDeposit/requestRedeem tx, poll until it settles or refunds: const settled = await waitForRequestSettlement(client.api, wallet, requestHash, { vaultId?, pollIntervalMs?: 5_000, timeoutMs?: 600_000, // throws SettlementTimeoutError on expiry }) ``` ```typescript type ActivityFlow = { kind: 'deposit' | 'withdraw' | 'transfer_in' | 'transfer_out' status: 'settled' | 'pending' | 'refunded' vaultId: string // CAIP-10 requestHash: string | null // Aera async correlation hash; null for sync flows requestedAt: Date | null settledAt: Date | null // null while pending assets: { decimal: string; raw: bigint | null; token: TokenRef | null } shares: bigint // 18-decimal base units txHashes: string[] } ``` `stitchActivityFlows(rows)` is the pure stitcher if you already have rows. ### Position history (high-level helper) Replays one vault's activity into a chronological position timeline — running share balance, escrowed pending amounts, cumulative net asset flows. ```typescript import { getPositionHistory } from '@gauntlet-xyz/sdk/api' const history = await getPositionHistory(client.api, wallet, apiVaultId) // { vaultId, token, points: PositionHistoryPoint[] } — oldest-first // Each point: { timestamp, txHash, type, sharesDelta, assetsDelta, sharesBalance, // pendingDepositAssets, pendingRedeemShares, netAssetsIn } // A wallet that never touched the vault gets an empty timeline, not an error. ``` `buildPositionHistory(rows)` is the pure builder. --- ## Privy — @gauntlet-xyz/sdk/privy Helpers for Privy embedded/connected wallets. No dependency on any `@privy-io` package. ```typescript import { createGauntletClientFromPrivy, walletClientFromPrivy } from '@gauntlet-xyz/sdk/privy' import { base } from 'viem/chains' // One-call setup: builds public clients per chain + a signing wallet from the Privy provider const { wallets } = useWallets() // @privy-io/react-auth const client = await createGauntletClientFromPrivy({ wallet: wallets[0], chains: [base], // first chain is the wallet's signing chain transports: { ... }, // optional per-chain viem Transport; defaults to public RPC http() apiKey, apiUrl, attributionMode, builderCode, // optional, same as GauntletClient }) // Or just wrap the Privy wallet in a viem WalletClient: const walletClient = await walletClientFromPrivy(wallets[0], base) ``` --- ## Transaction Submission `getDepositTx` and `getWithdrawTx` return `PreparedTx[]`. Steps MUST be executed in order. An `approve` step, when present, always comes first. ### PreparedTx type ```typescript type PreparedTx = { payload: { type: string // 'approve' | 'deposit' | 'requestDeposit' | 'redeem' | 'requestRedeem' | 'withdraw' | 'setDepositReceiverApproval' to: Address // contract address data: Hex // ABI-encoded calldata + attribution suffix pre-concatenated account?: Address } tx: EvmTxStep // structured ABI fields + raw attribution bytes } type EvmTxStep = { type: 'approve' | 'deposit' | 'requestDeposit' | 'redeem' | 'requestRedeem' | 'withdraw' | 'setDepositReceiverApproval' address: Address abi: Abi functionName: string args: readonly unknown[] account: Address attribution?: Hex // raw ERC-8021 suffix bytes only — NOT the full calldata } ``` --- ### Path 1: step.payload + sendTransaction **Use for:** backend scripts, embedded wallets (Privy, Dynamic), server-side signing, EVM simulation. Attribution is baked into `payload.data` — cannot be dropped regardless of wallet or provider. ```typescript for (const step of steps) { // estimateGas simulates exact calldata before broadcast — catches reverts before spending gas const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) // gas limit prevents out-of-gas failures on-chain const hash = await walletClient.sendTransaction({ ...step.payload, gas }) // wait for confirmation before next step — deposit reverts if preceding approve is not mined const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') throw new Error(`Reverted: ${step.payload.type}`) } ``` --- ### Path 2: step.tx + writeContract **Use for:** browser wallets via wagmi (MetaMask, Coinbase Wallet, WalletConnect). CRITICAL: `dataSuffix` MUST be `step.tx.attribution`. Omitting it silently drops attribution — the transaction succeeds on-chain but volume is NOT tracked. ```typescript import { useWriteContract } from 'wagmi' const { writeContractAsync } = useWriteContract() for (const step of steps) { await writeContractAsync({ address: step.tx.address, abi: step.tx.abi, functionName: step.tx.functionName, args: step.tx.args, account: step.tx.account, dataSuffix: step.tx.attribution, // REQUIRED — omitting silently drops attribution }) } ``` --- ## Attribution - Format: ERC-8021 — `0x8021` + UTF-8 hex of builderCode, e.g. `"acme"` → `0x802161636d65` - Appended as suffix to calldata. Does not affect contract execution. - `builderCode` MUST be issued by Gauntlet. Unregistered codes append bytes but are not counted. - Without `builderCode`, no bytes are appended and transactions are unattributed. - `AttributionMode.ENCODED` and `AttributionMode.PRIVATE` throw `UnimplementedFeatureError`. --- ## VaultId Enum Well-known vault ids. The bundled manifest contains more vaults than this enum — any manifest `vaultId` string is accepted wherever `VaultId` is; use `getVaults(client)` to enumerate everything. The Gaia API indexes even more vaults than the manifest lists. ```typescript enum VaultId { BaseUsdcPrime = 'baseUsdcPrime', EthUsdcPrime = 'ethUsdcPrime', AeraUsdAlpha = 'gtusda', // gtUSDa — multi-chain USDC, Aera async EthUsdcPrimeV2 = 'ethUsdcPrimeV2', AeraUsdAlphaStaging = 'stgusda', AeraUsdAlphaDev = 'devusda', AeraUsdAlphaDevDeux = 'devusda2', AeraLeveredFalconX = 'gpaafalconx', AeraLeveredFalconXStaging = 'pytstg', AeraSyrupUsdc = 'gpsyrupusdc', AeraSyrupUsdcStaging = 'syruppytstg', AeraBtcYield = 'gtbtc', AeraBtcYieldStaging = 'gtbtcstaging', AeraLeveredUsccStaging = 'gtusccstg', AeraLeveredUscc = 'gtuscc', AeraLend = 'gtlend', AeraKastEth = 'kasteth', } ``` --- ## Types ```typescript type VaultInfo = { vaultId: string name: string protocol: 'aera' | 'morpho' strategy: string deployments: VaultDeployment[] } type VaultDeployment = { // currently always EvmVaultDeployment; narrow on `chain` chain: 'evm' chainId: number vaultAddress: Address vaultType: 'single-depositor' | 'multi-depositor' supplyToken: TokenInfo[] expirationDays?: number // days before an async request deadline expires; default 3 } // NOTE: provisioner/feeCalculator addresses and sync/async support are NOT in the // manifest — they are resolved from live chain state (see Key Constraints). type TokenInfo = { symbol: string address: Address decimals: number } type UserCurrentBalance = { chain: string // 'base' | 'ethereum' | 'arbitrum' | 'optimism' token: Address decimals: number pendingDeposit: bigint // assets locked in provisioner after async deposit — awaiting solver, not yet earning yield; 0n if none balance: bigint // assets actively earning in the vault; 0n if no position pendingWithdraw: bigint // assets redeemed but not yet claimable after async withdraw — awaiting solver, no longer earning; 0n if none } // All three bigint fields are always present. 0n means no position — not an error. type VaultFilter = { chainId?: number protocol?: string } enum AttributionMode { PUBLIC = 'public', // ERC-8021 builder code — default ENCODED = 'encoded', // NOT IMPLEMENTED — throws UnimplementedFeatureError PRIVATE = 'private', // NOT IMPLEMENTED — throws UnimplementedFeatureError } const ContractVersion = { V1: 'v1', V2: 'v2' } // Aera provisioner/feeCalculator generations ``` --- ## Balance Lifecycle Pending states represent funds in transit — locked in the provisioner contract and awaiting the vault solver. During `pendingDeposit`, assets are not yet earning yield. During `pendingWithdraw`, vault shares have been redeemed and assets are no longer earning yield but have not yet been transferred. Funds are safe in both pending states; the delay (~2h, up to 12h) is operational, not a risk. | Event | Effect on balance fields | |-------|--------------------------| | Async deposit submitted | Amount enters `pendingDeposit` — locked in provisioner, not yet earning | | Solver processes deposit (~2h, up to 12h) | `pendingDeposit` → `balance` — now earning yield | | Sync deposit | Goes directly to `balance` — earning immediately | | Async withdraw submitted | `balance` → `pendingWithdraw` — shares redeemed, no longer earning, not yet claimable | | Solver processes withdraw (~2h, up to 12h) | Leaves `pendingWithdraw`; claimable as ERC-20 in receiver wallet | | Sync withdraw | Removed from `balance` immediately; claimable in receiver wallet. Aera vaults: blocked for 1 hour after the user's most recent sync deposit | --- ## Errors All extend `GauntletSDKError extends Error`. | Class | Properties | Thrown when | |-------|------------|-------------| | `VaultNotFoundError` | `.vaultId`, `.chainId?` | Vault ID not in manifest or not on requested chain | | `UnsupportedAssetError` | `.asset`, `.vaultId` | Token not accepted by vault | | `ChainMismatchError` | `.expected`, `.received` | chainId param doesn't match deployment | | `UnsupportedDepositModeError` | `.vaultId`, `.requested`, `.available` | Requested sync/async not supported | | `RpcNotConfiguredError` | `.chainId` | No evmClients entry for required chain | | `AccountRequiredError` | — | No wallet in client config | | `UnsupportedProtocolError` | `.protocol` | getUserCurrentBalance called on non-Aera or single-depositor vault | | `InvalidWithdrawParamsError` | — | Not exactly one of shares/amount/entireAmount provided | | `InvalidSlippageBPSError` | `.slippage` | slippageBps not an integer in 0–10000 | | `InvalidSolverTipError` | `.solverTip`, `.availableAmount` | solverTip ≥ available token amount | | `StalePriceError` | `.blockTimestamp`, `.maxPriceAge`, `.priceTimestamp` | Vault's on-chain price older than its max age | | `UnimplementedFeatureError` | `.feature` | ENCODED/PRIVATE attribution modes, getSourceId | | `UnsupportedFeatureError` | `.feature` | Feature not supported (e.g. separate receiver / receiver approval on Aera V1) | | `UnitConversionError` | `.vaultAddress` | Fee calculator unavailable on-chain | | `GauntletApiError` | `.status`, `.path`, `.code?` | Gaia API request failed (non-2xx) | | `InvalidDecimalError` | `.value` | String is not a valid decimal | | `DecimalPrecisionError` | `.value`, `.decimals` | Conversion would lose precision (never rounds) | | `InvalidCaipIdError` | `.id` | Not a valid `"{chainId}:{address}"` vault id | | `SettlementTimeoutError` | `.requestHash`, `.timeoutMs` | waitForRequestSettlement deadline expired | --- ## Key Constraints - The SDK never signs. `wallet` is read-only for `wallet.account.address`. - `getDepositTx`/`getWithdrawTx` require `evmClients` + `wallet` (check allowance on-chain). - `getVaults` reads bundled manifest — no network call required. - **Aera runtime is resolved from live chain state**, not the manifest: provisioner and fee-calculator addresses are read from the vault contract, and V1 vs V2 is detected on-chain (`resolveAeraRuntimeContracts` / `resolveContractVersion`, exported for advanced use alongside price/unit utilities `convertTokenToUnits`, `convertUnitsToToken`, `getVaultState`, `isVaultPaused`). - **Deposit mode:** sync/async availability is read from live vault configuration. When both are available and `depositMode` is omitted, async is used. Morpho vaults are sync-only. - **Aera receiver rules:** on V1 vaults, `receiver` MUST equal the signer — a different address throws `UnsupportedFeatureError`. V2 vaults support a separate receiver; for **sync** deposits to a separate receiver, the receiver must first sign `getDepositReceiverApprovalTx` approving the depositor. On withdrawals, `receiver` is where the assets are sent; shares always burn from the signer. - **Unit lock after sync deposit:** on Aera vaults, a sync deposit locks all of the user's vault units for the vault's deposit refund timeout, currently 1 hour. Any withdraw (sync or async) or unit transfer within that window reverts on-chain with `Aera__UnitsLocked`. Async deposits do not trigger the lock. - `getUserCurrentBalance` scans ~3 days of provisioner events — requires `evmClients` for each chain queried. - **Multi-chain vaults** (e.g. gtUSDa): `chainId` defaults to Base (8453) when omitted. - **Single-chain vaults:** provide `chainId` matching the deployment, or omit it. A mismatched chainId throws `ChainMismatchError`. - Steps from `getDepositTx`/`getWithdrawTx` must be sent in order. Deposit reverts if preceding approve is not mined first. - Using Path 2 (writeContract): `dataSuffix` must be `step.tx.attribution` or attribution is silently lost. - **Gaia API:** vault ids are CAIP-10 (`"{chainId}:{address}"`, lowercase) — convert with `apiVaultIdFromVaultId`; amounts are human-unit decimal strings — convert with `decimalToBigInt`/`sharesToBigInt` (shares are always 18 decimals); pages cap at 1000 rows (cursor via `meta.next_cursor`); anonymous access is rate-limited. ```` # Get historical token prices Source: https://docs.gauntlet.xyz/api-reference/prices/get-historical-token-prices GET /v1/prices/timeseries Returns a USD price timeseries for a token. Query parameters: `address` (required), `chain_id` (required), `start` (required ISO 8601), `end` (required ISO 8601), `granularity` (optional: `raw` | `hour` | `day` | `week` | `month`), `limit` (optional, default 1000, max 5000). # Get latest token price Source: https://docs.gauntlet.xyz/api-reference/prices/get-latest-token-price GET /v1/prices Returns the latest USD price for a token. Query parameters: `address` (required), `chain_id` (required), `at` (optional ISO 8601 timestamp). # List curated strategies Source: https://docs.gauntlet.xyz/api-reference/strategies/list-curated-strategies GET /v1/strategies Returns one card per curated strategy (Earn, Prime Lending, …) with aggregate metrics across the strategy's visible vaults: total TVL in USD, the highest 7/30/90-day APY ("APY up to"), and the vault count. TVL sums only vaults whose numeraire has a USD price; unpriced vaults still count toward `vault_count` and the APY maxes. Strategy membership and visibility are curated by Gauntlet; metrics come from the indexer and are refreshed continuously. Ordered by curated display order. # Get live aggregate TVL Source: https://docs.gauntlet.xyz/api-reference/tvl/get-live-aggregate-tvl GET /v1/tvl Returns live aggregate Gauntlet TVL composed from indexed Gaia vault data and configured external TVL sources. Source totals are always returned; pass `?include_breakdown=true` for chain and external-source explainability rows. # Get all current positions for a wallet Source: https://docs.gauntlet.xyz/api-reference/users/get-all-current-positions-for-a-wallet GET /v1/users/{wallet_address}/positions Returns every vault position the wallet currently holds or has pending exposure in (pending deposit or redeem). Fully-exited positions (zero shares, no pending escrow) are excluded. Scoped to the Gauntlet-curated vault set: only positions in publicly listed vaults are returned by default, `include_hidden=true` widens to hidden (enabled but unlisted) vaults, and positions in disabled vaults are never returned. All monetary metrics include a `usd` field (null when pricing is unavailable). `value.usd` and `pending_deposit_assets.usd` use the current spot price. `cost_basis.usd` and `pnl.realized.usd` are computed by replaying on-chain events against the token's historical price series — the same method used by `GET /v1/users/{wallet}/positions/{vault_id}`. `pnl.unrealized.usd`, `pnl.total.usd`, and `roi_pct.usd` are derived from those. Cursor-paginated on internal position ID. Default page size 100, max 500. # Get user position in a specific vault Source: https://docs.gauntlet.xyz/api-reference/users/get-user-position-in-a-specific-vault GET /v1/users/{wallet_address}/positions/{vault_id} Returns the current position snapshot for a wallet in a single vault. Historical points live on `/positions/{vault_id}/timeseries`. # Get user position timeseries for a specific vault Source: https://docs.gauntlet.xyz/api-reference/users/get-user-position-timeseries-for-a-specific-vault GET /v1/users/{wallet_address}/positions/{vault_id}/timeseries Returns historical value, cost basis, grouped PnL, and ROI data for a single vault position. Default order is `asc` (oldest first, chart-friendly); pass `?order=desc` for newest-first list views. Cursor-paginated — pass `meta.next_cursor` back as `?next=` for the next page (cursor is bound to the order it was created with). Defaults to `granularity=day` (one UTC-midnight snapshot per day); `week` returns Monday 00:00 UTC snapshots, `month` returns first-of-month 00:00 UTC snapshots, and `hour` returns the raw hourly cadence. Default page size is 1000. # Get user wallet activity (deposits, withdrawals, transfers) Source: https://docs.gauntlet.xyz/api-reference/users/get-user-wallet-activity-deposits-withdrawals-transfers GET /v1/users/{wallet_address}/activity Immutable log of on-chain events that affected the wallet's vault position. Each row is a frozen-in-time record; rows never mutate after emission. `?vault_id=` narrows to one vault; omitted = activity across every vault the wallet has touched, scoped to the Gauntlet-curated vault set (listed vaults by default, `include_hidden=true` widens to hidden vaults; disabled vaults never appear). Each row echoes its `vault_id` so wallet-wide consumers can distinguish per-vault activity. Async deposit/redeem lifecycles emit multiple rows (one at request time with `status=pending`, a later one with `status=settled` or `refunded`). Consumers correlate them via `request_hash` to follow a single action across rows. Sync flows emit one row directly in the settled state. Pagination is cursor-only. Default order is `desc` (newest first). Default page size 100, max 1000. # Get current vault allocations Source: https://docs.gauntlet.xyz/api-reference/vaults/get-current-vault-allocations GET /v1/vaults/{vault_id}/allocations Returns current allocations through a Gaia-owned, source-independent contract. Aera API is the initial source, but its metric-group schema is normalized into flat allocation values with discriminated targets. Use `/{vault_id}` or `/{vault_id}/timeseries` for vault-level metrics. # Get current vault metrics Source: https://docs.gauntlet.xyz/api-reference/vaults/get-current-vault-metrics GET /v1/vaults/{vault_id} Returns the current metrics snapshot for a vault — same metric shape that `/timeseries` emits per point, so this is `current point` and timeseries is `historical points`. The vault's Admin grouping is included when the deployment is associated. The protocol-specific definition (fees, hooks, curator, etc.) lives on `/{vault_id}/definition`. # Get vault definition Source: https://docs.gauntlet.xyz/api-reference/vaults/get-vault-definition GET /v1/vaults/{vault_id}/definition Returns the vault's identity + protocol-specific definition: name, owner, numeraire token, hooks, fees, curator, etc., merged inline based on `vault_type`. Aera carries hooks/feeCalculator, Morpho V1/V2 carry curator + WAD-scaled fees, Symbiotic carries identity only. # Get vault deployments by slug Source: https://docs.gauntlet.xyz/api-reference/vaults/get-vault-deployments-by-slug GET /v1/vaults/slug/{slug} Returns every indexed deployment associated with one exact Admin vault slug. The lookup serves visible and hidden enabled vaults without making hidden vaults enumerable; disabled and unknown slugs return 404. Each item has the same shape as an item from `GET /v1/vaults`. # Get vault timeseries data Source: https://docs.gauntlet.xyz/api-reference/vaults/get-vault-timeseries-data GET /v1/vaults/{vault_id}/timeseries Returns historical metric data points. Default order is `asc` (oldest first, chart-friendly); pass `?order=desc` for newest-first list views. Supports date range filtering and cursor pagination — pass `meta.next_cursor` back as `?next=` for the next page (cursor is bound to the order it was created with). Defaults to `granularity=day` (one UTC-midnight snapshot per day); `week` returns Monday 00:00 UTC snapshots, `month` returns first-of-month 00:00 UTC snapshots, and `hour` returns the raw hourly cadence. Default page size is 1000. # List featured vaults Source: https://docs.gauntlet.xyz/api-reference/vaults/list-featured-vaults GET /v1/vaults/featured Returns the admin-curated featured shortlist, one card per logical vault in curated order: display name, marketing description, deployed chains (highest-TVL first), supply token, aggregate TVL, and the highest 7/30/90-day APY across deployments. `vault_ids` carries per-deployment `{chainId}:{address}` ids for `/v1/vaults/{vault_id}` links, in the same order as `chains`. # List Gauntlet-curated vaults Source: https://docs.gauntlet.xyz/api-reference/vaults/list-gauntlet-curated-vaults GET /v1/vaults Returns identification-only rows for the admin-curated visible vaults, ordered most-recent-first; pass `?include_hidden=true` for every enabled vault (hidden included, disabled never). Each row's `group.id` groups deployments of the same logical Admin vault, `group.slug` is its unique public route, and `group.is_primary` identifies its representative. Use `/{vault_id}` for current metrics, `/{vault_id}/definition` for the full vault definition, `/{vault_id}/timeseries` for history. Cursor-paginated, but the default page size is the cap so most callers don't need to think about it — pass `meta.next_cursor` back as `?next=` only if the result exceeds 1000 vaults. # Attribution with SDK Source: https://docs.gauntlet.xyz/attribution/attribution-with-sdk How the Gauntlet SDK tracks on-chain volume back to your integration using ERC-8021 builder codes, and how to monitor attributed activity. Attribution links on-chain deposit and withdrawal volume back to your integration — enabling fee sharing and volume reporting in the Developer Portal. The SDK embeds attribution automatically on every transaction it builds. ## Setup **You must request a builder code from Gauntlet** — it is not self-serve. Request one as part of your partnership onboarding. The Gauntlet indexer must recognize your specific code for volume to be counted against your integration; using an arbitrary string will append bytes to calldata but attribution will not be tracked. Your builder code is separate from your API key — you may receive them together or at different stages of onboarding. Once you have it, set `builderCode` on `GauntletClient`: ```typescript theme={null} import { GauntletClient, AttributionMode } from '@gauntlet-xyz/sdk' const client = new GauntletClient({ evmClients: { ... }, wallet: walletClient, builderCode: 'your-builder-code', // issued by Gauntlet — request from the partnerships team attributionMode: AttributionMode.PUBLIC, // default — no need to set explicitly }) ``` Without `builderCode`, `AttributionMode.PUBLIC` appends nothing and transactions are unattributed. ## How It Works The SDK encodes attribution as an ERC-8021 calldata suffix appended to every transaction. It does not affect contract execution — the Gauntlet indexer reads it to attribute the volume. **ERC-8021 format:** ``` 0x{builderCode as UTF-8 hex}{byte length (1 byte)}{schema ID: 00}{16-byte marker: "8021" × 8} ``` For example, builder code `"acme"` (4 bytes) becomes `0x61636d650400` followed by the 16-byte marker `80218021802180218021802180218021`. ## Sending Transactions How attribution is carried depends on which submission path you use. ### Path 1 — `step.payload` + `sendTransaction` Attribution is **baked into `payload.data`** — the full calldata is already `{ABI-encoded call}{attribution suffix}`. The wallet receives a single opaque hex string and sends it as-is. Attribution cannot be lost regardless of what wallet or provider you use. ```typescript theme={null} for (const step of steps) { // payload.data = ABI calldata + attribution suffix, pre-concatenated await walletClient.sendTransaction(step.payload) } ``` **Best for:** backend scripts, server-side signing, embedded wallets (Privy, Dynamic, passkey signers), any flow that uses `eth_sendRawTransaction` directly, or anywhere you want `eth_call` pre-simulation on the exact calldata that will be broadcast. ### Path 2 — `step.tx` + `writeContract` Attribution is in `step.tx.attribution` as standalone bytes. You must pass it as `dataSuffix` to `writeContract` — wagmi appends it to the ABI-encoded calldata before sending. **If `dataSuffix` is omitted or the EIP-1193 provider strips it, the transaction succeeds on-chain but volume is not attributed.** ```typescript theme={null} for (const step of steps) { await walletClient.writeContract({ address: step.tx.address, abi: step.tx.abi, functionName: step.tx.functionName, args: step.tx.args, account: step.tx.account, dataSuffix: step.tx.attribution, // ← required for attribution; omitting silently drops it }) } ``` **Best for:** browser wallets via wagmi (MetaMask, Coinbase Wallet, WalletConnect), or when you need wagmi's type-safe simulation hooks (`simulateContract`). **Note:** Most major EIP-1193 providers used with wagmi honor `dataSuffix`. If you're using a custom or obscure provider, verify it forwards unknown `WriteContractParameters` fields before relying on this path for attribution. ## Attribution Modes ```typescript theme={null} enum AttributionMode { PUBLIC = 'public', // ERC-8021 builder code appended — default ENCODED = 'encoded', // not yet implemented PRIVATE = 'private', // not yet implemented } ``` Currently only `PUBLIC` is supported. `ENCODED` and `PRIVATE` throw `UnimplementedFeatureError` if used. ## Monitor Attributed Activity Monitoring attribution is under development and not currently available. ### Initialize ```typescript theme={null} import { GauntletClient } from '@gauntlet-xyz/sdk' const client = new GauntletClient({ apiKey: process.env.GAUNTLET_API_KEY, }) ``` ### Query Attributed Activity ```typescript API theme={null} const API_BASE_URL = 'https://api.gauntlet.xyz' const API_KEY = process.env.GAUNTLET_API_KEY! const { data: txns } = await fetch( `${API_BASE_URL}/v1/users/0xUserWallet/transactions?limit=20`, { headers: { Authorization: `Bearer ${API_KEY}` } }, ).then(r => r.json()) // returns: // [ // { // type: "deposit", // vault_id: "8453:0x000000000001cdb57e58fa75fe420a0f4d6640d5", // chain_id:address // amount: "1000.00", // timestamp: "2026-01-15T10:30:00Z" // }, // ... // ] const { data: events } = await fetch( `${API_BASE_URL}/v1/events?type=deposit&limit=100`, { headers: { Authorization: `Bearer ${API_KEY}` } }, ).then(r => r.json()) // returns: // [ // { // vault_id: "8453:0x000000000001cdb57e58fa75fe420a0f4d6640d5", // amount: "1000.00", // sender: "0x...", // timestamp: "2026-01-15T10:30:00Z" // }, // ... // ] ``` Use the vault's `chain_id:address` string (e.g. gtBTC on Ethereum is `1:0xeff0ae5b39271b33f448cd408b51dc8aa72a672b`) to filter events by vault: `GET /v1/events?vault_id=1:0xeff0ae5b39271b33f448cd408b51dc8aa72a672b`. Use **transactions** for user-facing activity and confirmations. Use **events** for monitoring, reconciliation, and reporting. ### Trend Activity Over Time ```typescript API theme={null} const params = new URLSearchParams({ field: 'amount', agg: 'sum', grain: 'day', type: 'deposit', limit: '30', }) const { data: points } = await fetch( `${API_BASE_URL}/v1/events/timeseries?${params}`, { headers: { Authorization: `Bearer ${API_KEY}` } }, ).then(r => r.json()) // returns: // [ // { timestamp: "2026-03-01", value: "1500000" }, // { timestamp: "2026-03-02", value: "1200000" }, // ... // ] ``` ### Monitoring Options | Approach | Best for | | ------------------------- | ----------------------------------------------------------- | | **API** | Product analytics, reporting, reconciliation — fastest path | | **Developer Portal** | Internal ops review and quick checks | | **On-chain verification** | Trustless audit or custom indexers | ### On-Chain Verification For trustless monitoring, read events directly from the gtUSDa vault on Base: ```typescript theme={null} import { createPublicClient, http, parseAbiItem } from 'viem' import { base } from 'viem/chains' const client = createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!), }) const depositEvent = parseAbiItem( 'event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)' ) const GTUSDA_VAULT = '0x000000000001CdB57E58Fa75Fe420a0f4D6640D5' const logs = await client.getLogs({ address: GTUSDA_VAULT, event: depositEvent, fromBlock: 'latest', }) // returns: // [ // { // args: { // sender: "0x...", // assets: 1000000n, // shares: 968000000000000000000n // }, // blockNumber: 12345678n, // transactionHash: "0x..." // } // ] ``` ## What's Next Add attribution manually using Privy embedded wallets or wagmi. Full result shape, type definitions, and error reference. SDK-based deposit and withdrawal flow using the Gauntlet SDK. gtUSDa contract addresses for on-chain verification. # Attribution without SDK Source: https://docs.gauntlet.xyz/attribution/attribution-without-sdk Manually add deposit and withdrawal attribution using an embedded wallet or wagmi, without installing the Gauntlet SDK. Attribution links your deposit and withdrawal volume to your integration — enabling fee sharing and volume reporting. If you cannot install the Gauntlet SDK, or have an existing integration that you do not want to migrate, you can add attribution manually using the [ERC-8021](https://www.erc8021.com/) standard. **You must request a builder code from Gauntlet** — using an unregistered string appends bytes to calldata but volume will not be counted. Request your builder code during partnership onboarding. ## Encode the Attribution Suffix Attribution is an ERC-8021 calldata suffix: the marker `0x8021` followed by your builder code encoded as UTF-8 hex. Construct it once and reuse it across all transactions. ```typescript theme={null} import { toHex } from 'viem' // 16-byte ERC-8021 marker: "8021" repeated 8 times const ERC8021_MARKER = '80218021802180218021802180218021' const ERC8021_SCHEMA_ID = '00' // Schema 0: simple ASCII codes function encodeAttribution(builderCode: string): `0x${string}` { const codeHex = toHex(builderCode).slice(2) // UTF-8 hex, no 0x prefix const codeByteLen = codeHex.length / 2 const codeLengthHex = codeByteLen.toString(16).padStart(2, '0') return `0x${codeHex}${codeLengthHex}${ERC8021_SCHEMA_ID}${ERC8021_MARKER}` } const attribution = encodeAttribution('your-builder-code') // e.g. 'acme' → '0x61636d650400' + 16-byte marker ``` Once the builder code is encoded, it's ready to be included in the transaction payload. Depending on the tools used for signing, the process is different. The options are Wagmi (most common) and embedded wallets (ex. privy). ## With wagmi wagmi's `writeContract` accepts a `dataSuffix` parameter that it appends to the ABI-encoded calldata before submitting to the wallet. Pass the attribution string there — the suffix is preserved even when the underlying EIP-1193 provider re-encodes the transaction. ### Check and request token approval ```typescript theme={null} import { useReadContract, useWriteContract } from 'wagmi' const ERC20_ABI = [ { type: 'function', name: 'allowance', inputs: [ { name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }, ], outputs: [{ type: 'uint256' }], stateMutability: 'view', }, { type: 'function', name: 'approve', inputs: [ { name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }, ], outputs: [{ type: 'bool' }], stateMutability: 'nonpayable', }, ] as const const { data: allowance } = useReadContract({ address: TOKEN_ADDRESS, abi: ERC20_ABI, functionName: 'allowance', args: [address, PROVISIONER_ADDRESS], }) const { writeContractAsync } = useWriteContract() if (allowance !== undefined && allowance < amount) { await writeContractAsync({ address: TOKEN_ADDRESS, abi: ERC20_ABI, functionName: 'approve', args: [PROVISIONER_ADDRESS, amount], dataSuffix: attribution, // keep attribution consistent across all steps }) } ``` Wait for the approval to confirm before submitting the deposit — the deposit reverts if the allowance hasn't landed yet. ### Request a deposit ```typescript theme={null} const PROVISIONER_ABI = [{ type: 'function', name: 'requestDeposit', inputs: [ { name: 'token', type: 'address' }, { name: 'tokensIn', type: 'uint256' }, { name: 'minUnitsOut', type: 'uint256' }, { name: 'solverTip', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, { name: 'maxPriceAge', type: 'uint256' }, { name: 'isFixedPrice', type: 'bool' }, ], outputs: [], stateMutability: 'nonpayable', }] as const const DAY = 86400n const deadline = BigInt(Math.ceil(Date.now() / 1000)) + DAY * 3n await writeContractAsync({ address: PROVISIONER_ADDRESS, abi: PROVISIONER_ABI, functionName: 'requestDeposit', args: [ TOKEN_ADDRESS, amount, 0n, // minUnitsOut — set to 0 to accept any price, or calculate slippage tolerance 0n, // solverTip — set to 0 unless you want to incentivize faster settlement deadline, // 3 days from now DAY * 10n, // maxPriceAge — maximum oracle price age accepted false, // isFixedPrice ], dataSuffix: attribution, // required — omitting silently drops attribution }) ``` Deposits are queued and settled by the Gauntlet solver (\~2 hours). Check the user's `pendingDeposit` balance using the [API](/api-reference/users/get-user-position-in-a-specific-vault) to confirm the request landed. ### Request a withdrawal For withdrawals, the spender is the provisioner and the token being approved is the **vault share token** (`VAULT_ADDRESS`), not the asset or numeraire token. ```typescript theme={null} const VAULT_ABI = [{ type: 'function', name: 'balanceOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view', }] as const const REDEEM_ABI = [{ type: 'function', name: 'requestRedeem', inputs: [ { name: 'token', type: 'address' }, { name: 'unitsIn', type: 'uint256' }, { name: 'minTokensOut', type: 'uint256' }, { name: 'solverTip', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, { name: 'maxPriceAge', type: 'uint256' }, { name: 'isFixedPrice', type: 'bool' }, ], outputs: [], stateMutability: 'nonpayable', }] as const const { data: shares } = useReadContract({ address: VAULT_ADDRESS, abi: VAULT_ABI, functionName: 'balanceOf', args: [address], }) // Approve provisioner to spend vault shares if needed const { data: vaultAllowance } = useReadContract({ address: VAULT_ADDRESS, abi: ERC20_ABI, functionName: 'allowance', args: [address, PROVISIONER_ADDRESS], }) if (vaultAllowance !== undefined && shares !== undefined && vaultAllowance < shares) { await writeContractAsync({ address: VAULT_ADDRESS, abi: ERC20_ABI, functionName: 'approve', args: [PROVISIONER_ADDRESS, shares], dataSuffix: attribution, }) } if (shares === undefined || shares === 0n) return await writeContractAsync({ address: PROVISIONER_ADDRESS, abi: REDEEM_ABI, functionName: 'requestRedeem', args: [ TOKEN_ADDRESS, shares, // unitsIn 0n, // minTokensOut — set to 0 to accept any price, or calculate slippage tolerance 0n, // solverTip deadline, DAY * 10n, // maxPriceAge false, // isFixedPrice ], dataSuffix: attribution, // required — omitting silently drops attribution }) ``` ## With a Privy Wallet (Embedded) Embedded wallets expose a `sendTransaction` function that accepts raw transaction parameters including a `data` field. Encode the full calldata manually and append the attribution suffix before sending — attribution is baked into the bytes and cannot be stripped by the provider. ### Check and request token approval ```typescript theme={null} import { createPublicClient, encodeFunctionData, http } from 'viem' import { base } from 'viem/chains' const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) }) const ERC20_ABI = [ { type: 'function', name: 'allowance', inputs: [ { name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }, ], outputs: [{ type: 'uint256' }], stateMutability: 'view', }, { type: 'function', name: 'approve', inputs: [ { name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }, ], outputs: [{ type: 'bool' }], stateMutability: 'nonpayable', }, ] as const const allowance = await publicClient.readContract({ address: TOKEN_ADDRESS, abi: ERC20_ABI, functionName: 'allowance', args: [userAddress, PROVISIONER_ADDRESS], }) if (allowance < amount) { const approveCalldata = encodeFunctionData({ abi: ERC20_ABI, functionName: 'approve', args: [PROVISIONER_ADDRESS, amount], }) // Append attribution to the approval so every step carries your builder code await sendTransaction({ to: TOKEN_ADDRESS, data: `${approveCalldata}${attribution.slice(2)}`, }) } ``` Wait for the approval to confirm before submitting the deposit — the deposit reverts if the allowance hasn't landed yet. `sendTransaction` here refers to Privy's transaction function — either `useSendTransaction` from `@privy-io/react-auth` or the equivalent method on the embedded wallet object. ### Request a deposit ```typescript theme={null} import { encodeFunctionData } from 'viem' const PROVISIONER_ABI = [{ type: 'function', name: 'requestDeposit', inputs: [ { name: 'token', type: 'address' }, { name: 'tokensIn', type: 'uint256' }, { name: 'minUnitsOut', type: 'uint256' }, { name: 'solverTip', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, { name: 'maxPriceAge', type: 'uint256' }, { name: 'isFixedPrice', type: 'bool' }, ], outputs: [], stateMutability: 'nonpayable', }] as const const DAY = 86400n const deadline = BigInt(Math.ceil(Date.now() / 1000)) + DAY * 3n const depositCalldata = encodeFunctionData({ abi: PROVISIONER_ABI, functionName: 'requestDeposit', args: [ TOKEN_ADDRESS, amount, 0n, // minUnitsOut — set to 0 to accept any price, or calculate slippage tolerance 0n, // solverTip — set to 0 unless you want to incentivize faster settlement deadline, // 3 days from now DAY * 10n, // maxPriceAge — maximum oracle price age accepted false, // isFixedPrice ], }) // Append attribution suffix — strip '0x' before concatenating await sendTransaction({ to: PROVISIONER_ADDRESS, data: `${depositCalldata}${attribution.slice(2)}`, }) ``` Deposits are queued and settled by the Gauntlet solver (\~2 hours). Check the user's `pendingDeposit` balance using the [API](/api-reference/users/get-user-position-in-a-specific-vault) to confirm the request landed. ### Request a withdrawal For withdrawals, the spender is the provisioner and the token being approved is the **vault share token** (`VAULT_ADDRESS`), not the asset or numeraire token. Read the user's share balance from the vault. ```typescript theme={null} const VAULT_ABI = [{ type: 'function', name: 'balanceOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view', }] as const // Read the user's vault share balance const shares = await publicClient.readContract({ address: VAULT_ADDRESS, abi: VAULT_ABI, functionName: 'balanceOf', args: [userAddress], }) // Approve the provisioner to spend vault shares if needed const vaultAllowance = await publicClient.readContract({ address: VAULT_ADDRESS, abi: ERC20_ABI, functionName: 'allowance', args: [userAddress, PROVISIONER_ADDRESS], }) if (vaultAllowance < shares) { const approveCalldata = encodeFunctionData({ abi: ERC20_ABI, functionName: 'approve', args: [PROVISIONER_ADDRESS, shares], }) await sendTransaction({ to: VAULT_ADDRESS, data: `${approveCalldata}${attribution.slice(2)}`, }) } const REDEEM_ABI = [{ type: 'function', name: 'requestRedeem', inputs: [ { name: 'token', type: 'address' }, { name: 'unitsIn', type: 'uint256' }, { name: 'minTokensOut', type: 'uint256' }, { name: 'solverTip', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, { name: 'maxPriceAge', type: 'uint256' }, { name: 'isFixedPrice', type: 'bool' }, ], outputs: [], stateMutability: 'nonpayable', }] as const const redeemCalldata = encodeFunctionData({ abi: REDEEM_ABI, functionName: 'requestRedeem', args: [ TOKEN_ADDRESS, shares, // unitsIn — full vault share balance 0n, // minTokensOut — set to 0 to accept any price, or calculate slippage tolerance 0n, // solverTip deadline, // 3 days from now DAY * 10n, // maxPriceAge false, // isFixedPrice ], }) await sendTransaction({ to: PROVISIONER_ADDRESS, data: `${redeemCalldata}${attribution.slice(2)}`, }) ``` ## What's Next How the SDK handles attribution automatically and how to monitor attributed volume. SDK-based deposit and withdrawal flow using the Gauntlet SDK. # BaseVault Source: https://docs.gauntlet.xyz/contract-reference/base-vault Core vault contract -- guardian operations, Merkle verification, hooks, and pause ## Overview `BaseVault` is the abstract base contract that all Aera V3 vault types inherit from. It provides the core infrastructure for guardian-based operation execution, Merkle proof verification, configurable hooks, and emergency pause functionality. Both `SingleDepositorVault` and `MultiDepositorVault` extend `BaseVault` to add depositor-specific logic. The vault accepts batches of operations submitted by an authorized guardian. Each operation is validated against a Merkle tree that whitelists specific contract targets and calldata patterns. Hooks can intercept operations at multiple lifecycle points to enforce constraints such as slippage bounds, position limits, and approval cleanup. For a conceptual overview of how `BaseVault` fits into the Aera V3 architecture, see [Aera V3 Overview](/guides/concepts/supported-protocols/aera-v3/overview). ## Functions ### submit Submits a batch of operations for the guardian to execute atomically. Each operation is validated against the guardian's Merkle tree before execution. Submit hooks fire before and after the batch, and individual operations can have pre-hooks and post-hooks. **Signature:** ```solidity theme={null} function submit(Operation[] calldata operations) external ``` | Parameter | Type | Description | | ------------ | ------------- | ----------------------------------------- | | `operations` | `Operation[]` | Array of operations to execute atomically | ### pause Pauses all guardian operations on the vault. Can be called by the guardian or the vault owner as an emergency safety mechanism. **Signature:** ```solidity theme={null} function pause() external ``` ### unpause Resumes guardian operations after a pause. Can only be called by the vault owner. **Signature:** ```solidity theme={null} function unpause() external ``` ### setGuardianRoot Sets the Merkle root that defines the guardian's allowed operations. Only callable by the vault owner. **Signature:** ```solidity theme={null} function setGuardianRoot(bytes32 root) external ``` | Parameter | Type | Description | | --------- | --------- | ------------------------------------------- | | `root` | `bytes32` | New Merkle root encoding allowed operations | ### setSubmitHooks Configures the before-submit and after-submit hook contracts for the vault. Only callable by the vault owner. **Signature:** ```solidity theme={null} function setSubmitHooks(address beforeHook, address afterHook) external ``` | Parameter | Type | Description | | ------------ | --------- | --------------------------------------------- | | `beforeHook` | `address` | Contract to call before each submission batch | | `afterHook` | `address` | Contract to call after each submission batch | ### checkGuardianWhitelist Checks whether a guardian is still authorized on the vault's whitelist. Can be called by anyone. Removes the guardian if it fails the whitelist check. **Signature:** ```solidity theme={null} function checkGuardianWhitelist() external ``` ### guardian Returns the address of the vault's current guardian. **Signature:** ```solidity theme={null} function guardian() external view returns (address) ``` ### owner Returns the address of the vault owner. **Signature:** ```solidity theme={null} function owner() external view returns (address) ``` ### paused Returns whether the vault is currently paused. **Signature:** ```solidity theme={null} function paused() external view returns (bool) ``` ## Events ### Submitted Emitted when a guardian successfully submits a batch of operations. ```solidity theme={null} event Submitted(address indexed guardian, uint256 operationCount) ``` ### Paused Emitted when the vault is paused. ```solidity theme={null} event Paused(address indexed account) ``` ### Unpaused Emitted when the vault is unpaused. ```solidity theme={null} event Unpaused(address indexed account) ``` ### GuardianRootSet Emitted when the guardian's Merkle root is updated. ```solidity theme={null} event GuardianRootSet(bytes32 indexed root) ``` ### SubmitHooksSet Emitted when submit hooks are configured. ```solidity theme={null} event SubmitHooksSet(address beforeHook, address afterHook) ``` ## Errors ### Vault\_\_Paused Thrown when a guardian operation is attempted while the vault is paused. ```solidity theme={null} error Vault__Paused() ``` ### Vault\_\_InvalidProof Thrown when an operation's Merkle proof does not verify against the guardian root. ```solidity theme={null} error Vault__InvalidProof() ``` ### Vault\_\_NotGuardian Thrown when a non-guardian address attempts a guardian-only action. ```solidity theme={null} error Vault__NotGuardian() ``` ### Vault\_\_NotOwner Thrown when a non-owner address attempts an owner-only action. ```solidity theme={null} error Vault__NotOwner() ``` ## Inheritance `BaseVault` is the root of the Aera V3 vault hierarchy: * **BaseVault** -- Core guardian operations, Merkle verification, hooks, pause * [SingleDepositorVault](/contract-reference/single-depositor-vault) -- Adds single-depositor deposit/withdraw * [MultiDepositorVault](/contract-reference/multi-depositor-vault) -- Adds multi-depositor ERC-20 vault units and order solving This page was manually created as a baseline. Run the [contract reference generation pipeline](/contract-reference/overview#generation-pipeline) to update with complete NatSpec documentation from the Solidity source. # Deployed Vaults Source: https://docs.gauntlet.xyz/contract-reference/deployed-vaults Production vault deployments — addresses, provisioners, calculators, and accepted tokens All Gauntlet-managed Aera V3 vault deployments across supported chains. Each vault has three associated contracts: * **Vault** — The multi-depositor vault contract that holds assets and issues share units * **Provisioner** — Handles deposit requests and redemptions. Partners interact with this contract for deposits and withdrawals. * **Calculator** — PriceAndFeeCalculator contract for unit pricing and fee computation For contract-level technical details, see individual contract reference pages. For integration guidance, see the [Integrate](/integrate/index) tab or the [SDK](/sdk/overview). ## gtUSDa — Gauntlet USD Alpha Supply token: **USDC** | Contract | Address | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | Vault | [`0x3bd9248048df95db4fbd748c6cd99c1baa40bad0`](https://etherscan.io/address/0x3bd9248048df95db4fbd748c6cd99c1baa40bad0) | | Provisioner | [`0x74C4A66CE4F4779B11E7c63D42e51EEef3A80D11`](https://etherscan.io/address/0x74C4A66CE4F4779B11E7c63D42e51EEef3A80D11) | | Calculator | [`0x8F3FfA11CD5915f0E869192663b905504A2Ef4a5`](https://etherscan.io/address/0x8F3FfA11CD5915f0E869192663b905504A2Ef4a5) | | Contract | Address | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | Vault | [`0x000000000001CdB57E58Fa75Fe420a0f4D6640D5`](https://basescan.org/address/0x000000000001CdB57E58Fa75Fe420a0f4D6640D5) | | Provisioner | [`0x18CF8d963E1a727F9bbF3AEffa0Bd04FB4dBdA07`](https://basescan.org/address/0x18CF8d963E1a727F9bbF3AEffa0Bd04FB4dBdA07) | | Calculator | [`0x69dD4D44eed6BbC33B8A0bdFe17897Ab9044372e`](https://basescan.org/address/0x69dD4D44eed6BbC33B8A0bdFe17897Ab9044372e) | | Contract | Address | | ----------- | ---------------------------------------------------------------------------------------------------------------------- | | Vault | [`0x000000001DC8bd45d7E7829fb1c969cbe4D0D1eC`](https://arbiscan.io/address/0x000000001DC8bd45d7E7829fb1c969cbe4D0D1eC) | | Provisioner | [`0xDd4a42603E6d8E515C3468789375A98c376821b3`](https://arbiscan.io/address/0xDd4a42603E6d8E515C3468789375A98c376821b3) | | Calculator | [`0xD61ecfB5cEd67Ef4F01E0dfae591c838BfA33932`](https://arbiscan.io/address/0xD61ecfB5cEd67Ef4F01E0dfae591c838BfA33932) | | Contract | Address | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Vault | [`0x000000001DC8bd45d7E7829fb1c969cbe4D0D1eC`](https://optimistic.etherscan.io/address/0x000000001DC8bd45d7E7829fb1c969cbe4D0D1eC) | | Provisioner | [`0xCC923371F0d3A9cA75d98E767Df9dE1cdf5799Ef`](https://optimistic.etherscan.io/address/0xCC923371F0d3A9cA75d98E767Df9dE1cdf5799Ef) | | Calculator | [`0xFB6De307b11C50D8B8A0790cd5c82c620D574440`](https://optimistic.etherscan.io/address/0xFB6De307b11C50D8B8A0790cd5c82c620D574440) | ## gpAAFalconX — Gauntlet Levered FalconX Supply token: **AA\_FalconXUSDC** | Contract | Address | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | Vault | [`0x00000000d8f3d6c5DFeB2D2b5ED2276095f3aF44`](https://etherscan.io/address/0x00000000d8f3d6c5DFeB2D2b5ED2276095f3aF44) | | Provisioner | [`0x21994912f1D286995c4d4961303cBB8E44939944`](https://etherscan.io/address/0x21994912f1D286995c4d4961303cBB8E44939944) | | Calculator | [`0x8F3FfA11CD5915f0E869192663b905504A2Ef4a5`](https://etherscan.io/address/0x8F3FfA11CD5915f0E869192663b905504A2Ef4a5) | Ethereum only. ## gpSyrupUSDC — Gauntlet SyrupUSDC Supply token: **syrupUSDC** | Contract | Address | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | Vault | [`0x00000000D283e5f8294e7E2dc06b55D43e060F49`](https://etherscan.io/address/0x00000000D283e5f8294e7E2dc06b55D43e060F49) | | Provisioner | [`0xA582D1b9c74892100986b7f2913468FaF350ba41`](https://etherscan.io/address/0xA582D1b9c74892100986b7f2913468FaF350ba41) | | Calculator | [`0x8F3FfA11CD5915f0E869192663b905504A2Ef4a5`](https://etherscan.io/address/0x8F3FfA11CD5915f0E869192663b905504A2Ef4a5) | Ethereum only. ## gtBTC — Gauntlet BTC Yield Supply token: **cbBTC** | Contract | Address | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | Vault | [`0xefF0AE5b39271b33f448cD408b51DC8aA72a672b`](https://etherscan.io/address/0xefF0AE5b39271b33f448cD408b51DC8aA72a672b) | | Provisioner | [`0xD580c26F7bD8A8a66fd32a97Df2308C083b65d9c`](https://etherscan.io/address/0xD580c26F7bD8A8a66fd32a97Df2308C083b65d9c) | | Calculator | [`0x811C6f0eF2E8f8A409306DAE242ba70Bd4f2467D`](https://etherscan.io/address/0x811C6f0eF2E8f8A409306DAE242ba70Bd4f2467D) | Ethereum only. ## gtUSCC — Gauntlet Levered USCC Supply token: **USCC** | Contract | Address | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | Vault | [`0x02b6bFD9561aC669305A0af0e5b88D9Cf850Bf67`](https://etherscan.io/address/0x02b6bFD9561aC669305A0af0e5b88D9Cf850Bf67) | | Provisioner | [`0xa9cdBbaFD61bc29C80989e5805c41f611BA7a5DA`](https://etherscan.io/address/0xa9cdBbaFD61bc29C80989e5805c41f611BA7a5DA) | | Calculator | [`0xF814c56D1323D79875f2DdBB1daf935a8d8e6b78`](https://etherscan.io/address/0xF814c56D1323D79875f2DdBB1daf935a8d8e6b78) | Ethereum only. ## Vault Types Gauntlet deploys two primary vault types: * **Multi-Depositor Vault** — Accepts deposits from multiple users into a shared pool. Suited for broad partner integrations where end users deposit and withdraw independently. See [Multi-Depositor Vault](/contract-reference/multi-depositor-vault) for contract details and [Vault Types](/guides/concepts/vault-types) for a conceptual overview. * **Single-Depositor Vault** — Dedicated to a single depositor (typically an institution or protocol treasury). Provides isolated risk and custom strategy configuration. See [Single-Depositor Vault](/contract-reference/single-depositor-vault) for contract details. ## Related Pages Full contract interface documentation for all Aera V3 components. The SDK resolves vault and provisioner addresses automatically from the bundled manifest. # Factory Addresses Source: https://docs.gauntlet.xyz/contract-reference/factory-addresses Aera V3 deployment factory addresses by chain and factory type Factories are the on-chain entry points for deploying new Aera V3 vaults. Each chain has up to three factory variants corresponding to the three vault archetypes. * **Base Vault Factory** — Deploys the core vault implementation. Used as the foundational building block for all vault types. * **Single-Depositor Factory** — Deploys vaults owned and funded by a single entity (e.g. a DAO treasury or institutional depositor). * **Multi-Depositor Factory** — Deploys vaults that accept deposits from multiple users via the Provisioner pattern. For deployed vault instances, see [Deployed Vaults](/contract-reference/deployed-vaults). For vault type concepts, see [Vault Types](/guides/concepts/vault-types). | Factory | Address | | ------------------------ | ---------------------------------------------------------------------------------------------- | | Multi-Depositor Factory | [`0x29722c...b90B4F`](https://etherscan.io/address/0x29722cC9a1cACff4a15914F9bC274B46F3b90B4F) | | Single-Depositor Factory | [`0x8f1FdB...e25AEb`](https://etherscan.io/address/0x8f1FdB45160234d6E7e3653F5Af8e09A2Ce25AEb) | | Base Vault Factory | [`0x1A8E10...9741B9`](https://etherscan.io/address/0x1A8E10A9503e747Aeb81DA5941bCDa6C6a9741B9) | | Base Vault Factory | [`0xc97961...3c543c`](https://etherscan.io/address/0xc97961eb53430cf6f159cf10692d44a7983c543c) | | Factory | Address | | ------------------------ | ---------------------------------------------------------------------------------------------- | | Multi-Depositor Factory | [`0x29722c...b90B4F`](https://basescan.org/address/0x29722cC9a1cACff4a15914F9bC274B46F3b90B4F) | | Multi-Depositor Factory | [`0x0cdaef...cfe591`](https://basescan.org/address/0x0cdaefbda316eda913dc96d580ec0331e4cfe591) | | Multi-Depositor Factory | [`0x418c3c...c3fb02`](https://basescan.org/address/0x418c3c6b54246fb43ebd4953724a00dfb1c3fb02) | | Multi-Depositor Factory | [`0x53cb34...df8213`](https://basescan.org/address/0x53cb347901b38dbc848185c4a6d1cdad06df8213) | | Single-Depositor Factory | [`0x8f1FdB...e25AEb`](https://basescan.org/address/0x8f1FdB45160234d6E7e3653F5Af8e09A2Ce25AEb) | | Base Vault Factory | [`0x1A8E10...9741B9`](https://basescan.org/address/0x1A8E10A9503e747Aeb81DA5941bCDa6C6a9741B9) | | Base Vault Factory | [`0xc597de...a73568`](https://basescan.org/address/0xc597deb367d2b5886fac8f6262bf282b26a73568) | | Factory | Address | | ------------------------ | --------------------------------------------------------------------------------------------- | | Multi-Depositor Factory | [`0xd18830...d2BD4E`](https://arbiscan.io/address/0xd1883062629157Ff6Eae51ca355aCA4f52d2BD4E) | | Single-Depositor Factory | [`0xAfdc48...718762`](https://arbiscan.io/address/0xAfdc4876c7a6d69c196caC078c97d6357e718762) | | Base Vault Factory | [`0xbe351E...cC96d2`](https://arbiscan.io/address/0xbe351E10c68B6d08b057529eEE9CAE0dCecC96d2) | | Factory | Address | | ------------------------ | --------------------------------------------------------------------------------------------------------- | | Multi-Depositor Factory | [`0xd18830...d2BD4E`](https://optimistic.etherscan.io/address/0xd1883062629157Ff6Eae51ca355aCA4f52d2BD4E) | | Single-Depositor Factory | [`0xAfdc48...718762`](https://optimistic.etherscan.io/address/0xAfdc4876c7a6d69c196caC078c97d6357e718762) | | Base Vault Factory | [`0xbe351E...cC96d2`](https://optimistic.etherscan.io/address/0xbe351E10c68B6d08b057529eEE9CAE0dCecC96d2) | Some factory types have multiple deployed addresses per chain due to iterative versioning. All listed addresses are active and may have vaults deployed from them. Contact Gauntlet if you need to verify which factory produced a specific vault. # Fee Splitters Source: https://docs.gauntlet.xyz/contract-reference/fee-splitters Reference home for contracts that split fees or revenue across multiple recipients. Use this page as the reference home for fee splitter contracts in the Gauntlet stack. ## What Belongs Here When public splitter contracts are documented, this page should hold: * ABI and method reference * recipient configuration and distribution rules * deployment addresses * operational notes that sit alongside fee accounting ## Current Related Docs * [FeeVault](/contract-reference/fee-vault) for the current fee-accrual contract surface * [Fees](/integrate/fees) for product-level fee guidance * [Periphery](/contract-reference/periphery) for pricing and fee-calculation helpers Current public accounting docs in this repo are centered on `FeeVault`. Keep this page as the dedicated reference slot for splitter-specific specs as they are published. # FeeVault Source: https://docs.gauntlet.xyz/contract-reference/fee-vault Fee accrual, reporting, and distribution for vault guardians and operators ## Overview `FeeVault` manages fee accrual and distribution for Aera V3 vaults. Fees accrue over time based on vault performance and are calculated by a dedicated fee calculator contract -- `DelayedFeeCalculator` for single-depositor vaults or `PriceAndFeeCalculator` for multi-depositor vaults. The guardian or fee recipient can claim accrued fees, but cannot manipulate the fee calculation itself. Fee accrual uses time-delayed or snapshot-based mechanisms to prevent manipulation through timing. The vault owner configures fee parameters and recipient addresses. For the conceptual model of how fees work in the Aera V3 protocol, see [Curation](/guides/concepts/curation) and [Security](/guides/concepts/security). ## Functions ### reportFees Reports the current fee state to the vault. Called by the guardian or fee calculator to update accrued fee amounts based on the latest vault value. **Signature:** ```solidity theme={null} function reportFees(uint256 vaultValue) external ``` | Parameter | Type | Description | | ------------ | --------- | --------------------------------------- | | `vaultValue` | `uint256` | Current vault value for fee calculation | ### claimFees Claims accrued fees for the caller. The fee recipient can withdraw their accrued fees from the vault. **Signature:** ```solidity theme={null} function claimFees(address token, uint256 amount, address recipient) external ``` | Parameter | Type | Description | | ----------- | --------- | ----------------------------------- | | `token` | `address` | Token address to claim fees in | | `amount` | `uint256` | Amount of fees to claim | | `recipient` | `address` | Address to receive the claimed fees | ### setFeeRecipient Sets the address that receives guardian fees. Only callable by the vault owner. **Signature:** ```solidity theme={null} function setFeeRecipient(address recipient) external ``` | Parameter | Type | Description | | ----------- | --------- | -------------------------------- | | `recipient` | `address` | Address of the new fee recipient | ### accruedFees Returns the total accrued fees available for claiming. **Signature:** ```solidity theme={null} function accruedFees() external view returns (uint256) ``` ### feeRecipient Returns the current fee recipient address. **Signature:** ```solidity theme={null} function feeRecipient() external view returns (address) ``` ## Events ### FeesReported Emitted when fees are reported to the vault. ```solidity theme={null} event FeesReported(uint256 vaultValue, uint256 feesAccrued) ``` ### FeesClaimed Emitted when accrued fees are claimed. ```solidity theme={null} event FeesClaimed(address indexed token, uint256 amount, address indexed recipient) ``` ### FeeRecipientSet Emitted when the fee recipient is updated. ```solidity theme={null} event FeeRecipientSet(address indexed recipient) ``` ## Errors ### FeeVault\_\_InsufficientFees Thrown when a claim exceeds accrued fees. ```solidity theme={null} error FeeVault__InsufficientFees() ``` ### FeeVault\_\_NotAuthorized Thrown when an unauthorized address attempts a fee operation. ```solidity theme={null} error FeeVault__NotAuthorized() ``` ## Inheritance `FeeVault` is typically composed alongside a vault rather than inherited directly: * [BaseVault](/contract-reference/base-vault) uses `FeeVault` for fee accounting * `DelayedFeeCalculator` -- Time-delayed fee calculation for single-depositor vaults * `PriceAndFeeCalculator` -- Unit-based fee calculation for multi-depositor vaults See [Periphery](/contract-reference/periphery) for fee calculator contract details. This page was manually created as a baseline. Run the [contract reference generation pipeline](/contract-reference/overview#generation-pipeline) to update with complete NatSpec documentation from the Solidity source. # Hook Interfaces Source: https://docs.gauntlet.xyz/contract-reference/hooks Hook interfaces for extending vault behavior at lifecycle points ## Overview Hooks are extension points in the Aera V3 protocol that allow custom logic to run at specific moments during vault operations. The vault owner configures hooks, and they execute automatically as part of the vault's normal operation flow. Hooks can enforce constraints (slippage bounds, position limits), calculate fees, validate parameters, or perform any other custom logic without modifying core vault contracts. For a conceptual overview of how hooks work, see [Aera V3 Hooks](/guides/concepts/supported-protocols/aera-v3/hooks). This page documents the specific hook interfaces that contracts must implement. *** ## IBeforeTransferHook Transfer hooks run before token transfers within [MultiDepositorVault](/contract-reference/multi-depositor-vault) vaults. They enforce transfer restrictions, blocklists, compliance requirements, or lock-up periods on vault unit transfers between addresses. ### Functions #### beforeTransfer Called before every vault unit transfer. The hook can revert to prevent the transfer. **Signature:** ```solidity theme={null} function beforeTransfer(address from, address to, uint256 amount) external ``` | Parameter | Type | Description | | --------- | --------- | --------------------------------------- | | `from` | `address` | Address sending vault units | | `to` | `address` | Address receiving vault units | | `amount` | `uint256` | Number of vault units being transferred | *** ## ISubmitHooks Submit hooks run before and after an entire guardian submission batch. They operate on the full batch of operations and are useful for batch-level accounting, aggregate position checks, or state snapshots. The vault owner configures submit hooks via `setSubmitHooks` on the [BaseVault](/contract-reference/base-vault). ### IBeforeSubmitHook #### beforeSubmit Called once before the guardian's operation batch executes. Can perform batch-level validation or pre-processing. **Signature:** ```solidity theme={null} function beforeSubmit(Operation[] calldata operations) external ``` | Parameter | Type | Description | | ------------ | ------------- | --------------------------------------------- | | `operations` | `Operation[]` | The full batch of operations about to execute | ### IAfterSubmitHook #### afterSubmit Called once after all operations in the batch complete. Used for post-processing such as fee snapshots, position tracking, or state verification. **Signature:** ```solidity theme={null} function afterSubmit(Operation[] calldata operations) external ``` | Parameter | Type | Description | | ------------ | ------------- | ------------------------------------------ | | `operations` | `Operation[]` | The batch of operations that just executed | *** ## IOperationHook Operation hooks run during individual operations within a submission batch. They are encoded in the guardian's Merkle tree alongside the operation. ### IBeforeOperationHook #### beforeOperation Called before an individual operation's target call executes. Typically validates calldata parameters -- for example, checking swap slippage against the `OracleRegistry`. **Signature:** ```solidity theme={null} function beforeOperation(address target, bytes calldata data, bytes calldata hookData) external ``` | Parameter | Type | Description | | ---------- | --------- | --------------------------------------- | | `target` | `address` | Target contract of the operation | | `data` | `bytes` | Calldata for the operation | | `hookData` | `bytes` | Additional context from the Merkle tree | ### IAfterOperationHook #### afterOperation Called after an individual operation completes. Verifies post-conditions such as no residual token approvals or expected balance changes. **Signature:** ```solidity theme={null} function afterOperation(address target, bytes calldata data, bytes calldata hookData) external ``` | Parameter | Type | Description | | ---------- | --------- | --------------------------------------- | | `target` | `address` | Target contract of the operation | | `data` | `bytes` | Calldata that was executed | | `hookData` | `bytes` | Additional context from the Merkle tree | *** ## IBeforeClaimHook Claim hooks run before fee claims or other claim operations. They can enforce additional validation on who can claim and under what conditions. ### Functions #### beforeClaim Called before a fee claim executes. Can revert to prevent the claim. **Signature:** ```solidity theme={null} function beforeClaim(address claimer, address token, uint256 amount) external ``` | Parameter | Type | Description | | --------- | --------- | ---------------------------- | | `claimer` | `address` | Address attempting the claim | | `token` | `address` | Token being claimed | | `amount` | `uint256` | Amount being claimed | *** ## Common Use Cases | Hook | Use Case | Example | | ---------------------- | --------------------- | ----------------------------------------------------- | | `IBeforeOperationHook` | Slippage enforcement | Check swap output against `OracleRegistry` prices | | `IAfterOperationHook` | Approval cleanup | Verify no residual ERC-20 approvals after operations | | `IBeforeSubmitHook` | Position limits | Check aggregate exposure before batch executes | | `IAfterSubmitHook` | Fee snapshots | Snapshot vault state for fee calculation | | `IBeforeTransferHook` | Transfer restrictions | Enforce compliance blocklists on vault unit transfers | | `IBeforeClaimHook` | Claim validation | Enforce timing or authorization on fee claims | This page was manually created as a baseline. Run the [contract reference generation pipeline](/contract-reference/overview#generation-pipeline) to update with complete NatSpec documentation from the Solidity source. # Investment Contracts Source: https://docs.gauntlet.xyz/contract-reference/investment-contracts Reference home for other Gauntlet-managed investment, adapter, or accounting contracts. Use this page as the reference home for contracts that sit outside the vault core but still belong to the Gauntlet contract surface. ## What Belongs Here This page is the slot for: * investment-specific helper contracts * protocol-specific adapters beyond Morpho * accounting or routing helpers that are not part of the base vault surface * deployment and capability notes for those contracts ## Current Related Docs * [Gauntlet Contract Reference](/contract-reference/overview) for the broader contract map * [Contract Addresses](/contract-reference/addresses) for deployed contract lookups Keep this page as the catch-all reference home for non-core Gauntlet contracts until each surface is large enough to justify its own page. # Morpho Adapters Source: https://docs.gauntlet.xyz/contract-reference/morpho-adapters Reference home for Gauntlet contracts that connect vaults or strategies to Morpho. Use this page as the reference home for Morpho adapter contracts in the Gauntlet stack. ## What Belongs Here When adapter contracts are published, this page should hold: * adapter ABI and entrypoints * vault-to-adapter integration notes * deployment matrix by chain * configuration and capability notes ## Current Related Docs * [Morpho](/guides/concepts/supported-protocols/morpho) for protocol context * [Gauntlet Contract Reference](/contract-reference/overview) for the broader contract map Current public docs in this repo focus on vault core contracts. Keep this page as the dedicated reference slot for Morpho adapter specs as they are published. # MultiDepositorVault Source: https://docs.gauntlet.xyz/contract-reference/multi-depositor-vault Multi-depositor vault with order solving, tokenized vault units, and price reporting ## Overview `MultiDepositorVault` extends [BaseVault](/contract-reference/base-vault) to support multiple depositors with tokenized ERC-20 vault units. Depositors interact with the vault through the [Provisioner](/contract-reference/provisioner) contract, which manages minting, redeeming, and async order fulfillment. The vault itself holds assets and executes guardian operations, while the Provisioner handles the depositor-facing interface. Multi-depositor vaults use a more complex guardian model than single-depositor vaults. The guardian submits operations through the standard `submit` function inherited from `BaseVault`, but the vault also integrates with a `PriceAndFeeCalculator` for unit-based pricing and fee calculation. Price reporting uses managed accountant snapshots to prevent fee manipulation. Transfer hooks (`IBeforeTransferHook`) can be configured on the vault's ERC-20 units to enforce compliance, blocklists, or transfer restrictions. See [Hooks](/contract-reference/hooks) for hook interface details. ## Functions ### totalAssets Returns the total value of assets held by the vault, denominated in the vault's unit of account. Used for share price calculations. **Signature:** ```solidity theme={null} function totalAssets() external view returns (uint256) ``` ### convertToShares Converts an asset amount to the equivalent number of vault shares at the current exchange rate. **Signature:** ```solidity theme={null} function convertToShares(uint256 assets) external view returns (uint256 shares) ``` | Parameter | Type | Description | | --------- | --------- | --------------------------- | | `assets` | `uint256` | Amount of assets to convert | ### convertToAssets Converts a share amount to the equivalent number of assets at the current exchange rate. **Signature:** ```solidity theme={null} function convertToAssets(uint256 shares) external view returns (uint256 assets) ``` | Parameter | Type | Description | | --------- | --------- | --------------------------- | | `shares` | `uint256` | Amount of shares to convert | ### balanceOf Returns the vault unit balance for a given address. **Signature:** ```solidity theme={null} function balanceOf(address account) external view returns (uint256) ``` | Parameter | Type | Description | | --------- | --------- | ---------------- | | `account` | `address` | Address to query | ### totalSupply Returns the total supply of vault units. **Signature:** ```solidity theme={null} function totalSupply() external view returns (uint256) ``` ### transfer Transfers vault units between addresses. Transfer hooks fire before the transfer if configured. **Signature:** ```solidity theme={null} function transfer(address to, uint256 amount) external returns (bool) ``` | Parameter | Type | Description | | --------- | --------- | --------------------------------- | | `to` | `address` | Recipient address | | `amount` | `uint256` | Number of vault units to transfer | ### setTransferHook Sets the before-transfer hook contract for vault unit transfers. Only callable by the vault owner. **Signature:** ```solidity theme={null} function setTransferHook(address hook) external ``` | Parameter | Type | Description | | --------- | --------- | ------------------------------------------- | | `hook` | `address` | Contract implementing `IBeforeTransferHook` | ### setPriceCalculator Sets the price and fee calculator contract. Only callable by the vault owner. **Signature:** ```solidity theme={null} function setPriceCalculator(address calculator) external ``` | Parameter | Type | Description | | ------------ | --------- | ----------------------------------------------- | | `calculator` | `address` | Address of the `PriceAndFeeCalculator` contract | ## Events ### Transfer Standard ERC-20 transfer event for vault units. ```solidity theme={null} event Transfer(address indexed from, address indexed to, uint256 value) ``` ### TransferHookSet Emitted when the transfer hook is configured. ```solidity theme={null} event TransferHookSet(address indexed hook) ``` ### PriceCalculatorSet Emitted when the price calculator is configured. ```solidity theme={null} event PriceCalculatorSet(address indexed calculator) ``` ## Errors ### MultiDepositorVault\_\_TransferHookFailed Thrown when the before-transfer hook reverts during a vault unit transfer. ```solidity theme={null} error MultiDepositorVault__TransferHookFailed() ``` ### MultiDepositorVault\_\_InsufficientBalance Thrown when a transfer or redemption exceeds the sender's vault unit balance. ```solidity theme={null} error MultiDepositorVault__InsufficientBalance() ``` ## Inheritance * [BaseVault](/contract-reference/base-vault) -- Core guardian operations, Merkle verification, hooks, pause * **MultiDepositorVault** -- Adds multi-depositor ERC-20 units, pricing, transfer hooks This page was manually created as a baseline. Run the [contract reference generation pipeline](/contract-reference/overview#generation-pipeline) to update with complete NatSpec documentation from the Solidity source. # Periphery Contracts Source: https://docs.gauntlet.xyz/contract-reference/periphery Supporting infrastructure -- OracleRegistry, fee calculators, and utilities ## Overview Periphery contracts provide supporting infrastructure for the core Aera V3 vault system. They handle price feeds, fee calculation, and other utility functions that the core vaults depend on but are not part of the vault inheritance hierarchy. *** ## OracleRegistry The `OracleRegistry` is an ERC-7726 compatible registry of price oracles for asset pairs. Vaults and hooks use it to look up current prices for exchange rate calculations, slippage enforcement, and fee computation. ### Functions #### getOracle Returns the oracle address for a given asset pair. **Signature:** ```solidity theme={null} function getOracle(address base, address quote) external view returns (address oracle) ``` | Parameter | Type | Description | | --------- | --------- | ------------------- | | `base` | `address` | Base asset address | | `quote` | `address` | Quote asset address | **Returns:** Address of the registered oracle for the pair. #### getPrice Returns the current price for an asset pair from the registered oracle. **Signature:** ```solidity theme={null} function getPrice(address base, address quote) external view returns (uint256 price) ``` | Parameter | Type | Description | | --------- | --------- | ------------------- | | `base` | `address` | Base asset address | | `quote` | `address` | Quote asset address | **Returns:** Current price of base asset denominated in quote asset. #### setOracle Registers or updates an oracle for an asset pair. Only callable by the registry owner. **Signature:** ```solidity theme={null} function setOracle(address base, address quote, address oracle) external ``` | Parameter | Type | Description | | --------- | --------- | ----------------------- | | `base` | `address` | Base asset address | | `quote` | `address` | Quote asset address | | `oracle` | `address` | Oracle contract address | ### Events #### OracleSet Emitted when an oracle is registered or updated. ```solidity theme={null} event OracleSet(address indexed base, address indexed quote, address indexed oracle) ``` *** ## DelayedFeeCalculator Fee calculator for [SingleDepositorVault](/contract-reference/single-depositor-vault) vaults. Applies time-delayed fee accrual based on vault values reported by an accountant. The delay prevents fee manipulation through short-term vault value changes. ### Functions #### calculateFees Calculates accrued fees based on the current vault value and time elapsed since the last report. **Signature:** ```solidity theme={null} function calculateFees(uint256 vaultValue) external view returns (uint256 fees) ``` | Parameter | Type | Description | | ------------ | --------- | ------------------------------------------------- | | `vaultValue` | `uint256` | Current vault value as reported by the accountant | **Returns:** Amount of fees accrued. #### reportValue Reports the current vault value for fee calculation. Starts or updates the time-delayed fee accrual window. **Signature:** ```solidity theme={null} function reportValue(uint256 vaultValue) external ``` | Parameter | Type | Description | | ------------ | --------- | ------------------- | | `vaultValue` | `uint256` | Current vault value | *** ## PriceAndFeeCalculator Fee calculator for [MultiDepositorVault](/contract-reference/multi-depositor-vault) vaults. Uses unit-based pricing with managed accountant snapshots for fee computation. Integrates with the `OracleRegistry` for price lookups. ### Functions #### calculateFees Calculates accrued fees based on the current unit price and total supply. **Signature:** ```solidity theme={null} function calculateFees(uint256 unitPrice, uint256 totalSupply) external view returns (uint256 fees) ``` | Parameter | Type | Description | | ------------- | --------- | ---------------------------- | | `unitPrice` | `uint256` | Current price per vault unit | | `totalSupply` | `uint256` | Total supply of vault units | **Returns:** Amount of fees accrued. #### snapshot Takes a snapshot of the current vault state for fee calculation. Called by the guardian as part of the submit flow (typically via an `afterSubmit` hook). **Signature:** ```solidity theme={null} function snapshot(uint256 unitPrice, uint256 totalSupply) external ``` | Parameter | Type | Description | | ------------- | --------- | ---------------------------- | | `unitPrice` | `uint256` | Current price per vault unit | | `totalSupply` | `uint256` | Total supply of vault units | This page was manually created as a baseline. Run the [contract reference generation pipeline](/contract-reference/overview#generation-pipeline) to update with complete NatSpec documentation from the Solidity source. # Provisioner Source: https://docs.gauntlet.xyz/contract-reference/provisioner Depositor-facing entry and exit flows for multi-depositor vaults ## Overview The `Provisioner` contract is the depositor-facing entry point for [MultiDepositorVault](/contract-reference/multi-depositor-vault) vaults. It sits between depositors and the underlying vault, managing share accounting, deposit/redeem requests, and cross-chain entry and exit flows. Depositors interact with the Provisioner to mint vault units on deposit and burn them on redemption. The Provisioner supports two interaction models: synchronous deposits (immediate vault unit minting) and asynchronous requests (order-based fulfillment by a solver). The async model is essential for cross-chain operations where CCTP bridging introduces latency. For the conceptual overview of how the Provisioner fits into cross-chain vault operations, see [Cross-Chain](/guides/concepts/supported-protocols/aera-v3/cross-chain). ## Functions ### deposit Deposits assets and mints vault units synchronously. The Provisioner calculates the exchange rate using the `OracleRegistry` and mints units directly to the depositor. **Signature:** ```solidity theme={null} function deposit(uint256 assets, address receiver) external returns (uint256 shares) ``` | Parameter | Type | Description | | ---------- | --------- | ----------------------------------------- | | `assets` | `uint256` | Amount of underlying assets to deposit | | `receiver` | `address` | Address to receive the minted vault units | **Returns:** Number of vault units minted. ### mint Mints a specific number of vault units by depositing the required assets. **Signature:** ```solidity theme={null} function mint(uint256 shares, address receiver) external returns (uint256 assets) ``` | Parameter | Type | Description | | ---------- | --------- | ----------------------------------------- | | `shares` | `uint256` | Number of vault units to mint | | `receiver` | `address` | Address to receive the minted vault units | **Returns:** Amount of assets deposited. ### requestDeposit Places an asynchronous deposit request. A solver (typically the guardian) fulfills the request in a later transaction. Used for cross-chain deposits where bridging introduces latency. **Signature:** ```solidity theme={null} function requestDeposit(uint256 assets, address receiver, address owner) external returns (uint256 requestId) ``` | Parameter | Type | Description | | ---------- | --------- | --------------------------------------------- | | `assets` | `uint256` | Amount of assets to deposit | | `receiver` | `address` | Address to receive vault units when fulfilled | | `owner` | `address` | Address that owns the deposit request | **Returns:** Unique request ID for tracking. ### requestRedeem Places an asynchronous redemption request. The solver fulfills the request after the guardian completes exit flows and assets are available. **Signature:** ```solidity theme={null} function requestRedeem(uint256 shares, address receiver, address owner) external returns (uint256 requestId) ``` | Parameter | Type | Description | | ---------- | --------- | --------------------------------------------------- | | `shares` | `uint256` | Number of vault units to redeem | | `receiver` | `address` | Address to receive underlying assets when fulfilled | | `owner` | `address` | Address that owns the redemption request | **Returns:** Unique request ID for tracking. ### redeem Redeems vault units for underlying assets synchronously. **Signature:** ```solidity theme={null} function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) ``` | Parameter | Type | Description | | ---------- | --------- | ---------------------------------------- | | `shares` | `uint256` | Number of vault units to redeem | | `receiver` | `address` | Address to receive the underlying assets | | `owner` | `address` | Address that owns the vault units | **Returns:** Amount of underlying assets received. ### claimDeposit Claims vault units from a fulfilled deposit request. **Signature:** ```solidity theme={null} function claimDeposit(uint256 requestId, address receiver) external returns (uint256 shares) ``` | Parameter | Type | Description | | ----------- | --------- | ---------------------------------- | | `requestId` | `uint256` | ID of the deposit request to claim | | `receiver` | `address` | Address to receive the vault units | **Returns:** Number of vault units received. ### claimRedeem Claims underlying assets from a fulfilled redemption request. **Signature:** ```solidity theme={null} function claimRedeem(uint256 requestId, address receiver) external returns (uint256 assets) ``` | Parameter | Type | Description | | ----------- | --------- | ------------------------------------- | | `requestId` | `uint256` | ID of the redemption request to claim | | `receiver` | `address` | Address to receive the assets | **Returns:** Amount of assets received. ## Events ### Deposit Emitted when a synchronous deposit completes. ```solidity theme={null} event Deposit(address indexed sender, address indexed receiver, uint256 assets, uint256 shares) ``` ### DepositRequested Emitted when an async deposit request is placed. ```solidity theme={null} event DepositRequested(uint256 indexed requestId, address indexed owner, uint256 assets) ``` ### RedeemRequested Emitted when an async redemption request is placed. ```solidity theme={null} event RedeemRequested(uint256 indexed requestId, address indexed owner, uint256 shares) ``` ### DepositClaimed Emitted when a fulfilled deposit request is claimed. ```solidity theme={null} event DepositClaimed(uint256 indexed requestId, address indexed receiver, uint256 shares) ``` ### RedeemClaimed Emitted when a fulfilled redemption request is claimed. ```solidity theme={null} event RedeemClaimed(uint256 indexed requestId, address indexed receiver, uint256 assets) ``` ## Errors ### Provisioner\_\_RequestNotFulfilled Thrown when attempting to claim a request that has not yet been fulfilled by the solver. ```solidity theme={null} error Provisioner__RequestNotFulfilled(uint256 requestId) ``` ### Provisioner\_\_InsufficientAssets Thrown when the deposit amount is below the minimum or the vault cannot cover the redemption. ```solidity theme={null} error Provisioner__InsufficientAssets() ``` ### Provisioner\_\_NotOwner Thrown when an unauthorized address attempts to cancel or modify a request. ```solidity theme={null} error Provisioner__NotOwner() ``` ## Inheritance `Provisioner` is a standalone contract that interacts with `MultiDepositorVault`: * **Provisioner** -- Depositor-facing deposit/redeem and async request management * Uses [MultiDepositorVault](/contract-reference/multi-depositor-vault) for asset custody * Uses `OracleRegistry` for exchange rate calculations (see [Periphery](/contract-reference/periphery)) This page was manually created as a baseline. Run the [contract reference generation pipeline](/contract-reference/overview#generation-pipeline) to update with complete NatSpec documentation from the Solidity source. # SingleDepositorVault Source: https://docs.gauntlet.xyz/contract-reference/single-depositor-vault Single-depositor vault with direct deposit, withdraw, and execute capabilities ## Overview `SingleDepositorVault` extends [BaseVault](/contract-reference/base-vault) with direct deposit and withdraw functions for a single vault owner. It is designed for dedicated treasury management where one entity (the vault owner) deposits assets and a guardian executes strategy operations on their behalf. Unlike the multi-depositor model, `SingleDepositorVault` does not use tokenized vault units or a Provisioner. The vault owner interacts directly with the vault contract to deposit and withdraw assets. The guardian model, Merkle verification, hooks, and pause functionality are all inherited from `BaseVault`. Fee calculation for single-depositor vaults uses the `DelayedFeeCalculator`, which applies time-delayed fee accrual based on accountant-reported vault values. See [FeeVault](/contract-reference/fee-vault) for fee distribution details. ## Functions ### deposit Deposits assets into the vault. Only callable by the vault owner. Assets are held by the vault and managed by the guardian via submitted operations. **Signature:** ```solidity theme={null} function deposit(address token, uint256 amount) external ``` | Parameter | Type | Description | | --------- | --------- | -------------------------------------- | | `token` | `address` | Address of the ERC-20 token to deposit | | `amount` | `uint256` | Amount of tokens to deposit | ### withdraw Withdraws assets from the vault. Only callable by the vault owner. **Signature:** ```solidity theme={null} function withdraw(address token, uint256 amount, address recipient) external ``` | Parameter | Type | Description | | ----------- | --------- | --------------------------------------- | | `token` | `address` | Address of the ERC-20 token to withdraw | | `amount` | `uint256` | Amount of tokens to withdraw | | `recipient` | `address` | Address to receive the withdrawn tokens | ### execute Executes an arbitrary call from the vault. Only callable by the vault owner. This provides a direct execution path outside the guardian model for owner-level administrative actions. **Signature:** ```solidity theme={null} function execute(address target, bytes calldata data) external returns (bytes memory) ``` | Parameter | Type | Description | | --------- | --------- | --------------------- | | `target` | `address` | Contract to call | | `data` | `bytes` | Calldata for the call | ### setFeeCalculator Sets the fee calculator contract for this vault. Only callable by the vault owner. **Signature:** ```solidity theme={null} function setFeeCalculator(address calculator) external ``` | Parameter | Type | Description | | ------------ | --------- | ---------------------------------------------- | | `calculator` | `address` | Address of the `DelayedFeeCalculator` contract | ## Events ### Deposited Emitted when the vault owner deposits assets. ```solidity theme={null} event Deposited(address indexed token, uint256 amount) ``` ### Withdrawn Emitted when the vault owner withdraws assets. ```solidity theme={null} event Withdrawn(address indexed token, uint256 amount, address indexed recipient) ``` ### Executed Emitted when the vault owner executes a direct call. ```solidity theme={null} event Executed(address indexed target, bytes data, bytes result) ``` ## Errors ### SingleDepositorVault\_\_NotOwner Thrown when a non-owner address attempts a deposit, withdraw, or execute action. ```solidity theme={null} error SingleDepositorVault__NotOwner() ``` ## Inheritance * [BaseVault](/contract-reference/base-vault) -- Core guardian operations, Merkle verification, hooks, pause * **SingleDepositorVault** -- Adds single-depositor deposit/withdraw/execute This page was manually created as a baseline. Run the [contract reference generation pipeline](/contract-reference/overview#generation-pipeline) to update with complete NatSpec documentation from the Solidity source. # Curation Source: https://docs.gauntlet.xyz/guides/concepts/curation How risk tiers and market selection affect the vaults your users interact with Risk tiers help you describe to your users what level of risk-reward tradeoff a vault targets. Gauntlet actively curates which DeFi markets a vault can deploy into, how much capital goes to each, and under what conditions allocations change. This is why two vaults on the same protocol can have very different risk-return profiles. ## Risk Tiers Gauntlet organizes vaults into three tiers: ### Prime The most conservative tier. Prime vaults allocate to blue-chip markets with deep liquidity, established collateral assets (ETH, stETH, USDC, USDT), and proven oracle infrastructure. Prioritizes capital preservation over maximum returns. ### Balanced Moderate risk-reward. Balanced vaults access a broader set of markets, including mid-tier collateral and shorter track records. Position sizing offsets the incremental risk. ### Frontier Higher yield potential with correspondingly higher risk. Frontier vaults may allocate to newer protocols, less liquid markets, or novel collateral types. Per-market concentration limits manage tail risk. For risk tiers in practice, see the [Morpho](/guides/concepts/supported-protocols/morpho) page. ## What Gauntlet Evaluates Before including a market in a vault's allocation set, Gauntlet assesses: * **Smart contract risk** -- audit history, production maturity, incident record. * **Liquidity risk** -- depth, utilization patterns, stress behavior. * **Oracle risk** -- reliability, decentralization, manipulation resistance. * **Counterparty risk** -- borrower concentration, collateral governance exposure. * **Market parameters** -- [LLTV](/guides/concepts/glossary) thresholds, interest rate models, liquidation incentives. Markets that degrade on any dimension can be removed or have their allocation reduced. ## Exposure Controls Gauntlet enforces concentration limits, correlation constraints, and rebalancing triggers -- both off-chain (risk systems) and on-chain (hook validation). For on-chain enforcement details, see [Aera V3 Hooks](/guides/concepts/supported-protocols/aera-v3/hooks). # Glossary Source: https://docs.gauntlet.xyz/guides/concepts/glossary Key terms for integrating with Gauntlet vault infrastructure Integration-relevant terms used throughout this documentation. *** **Curation** -- The process by which Gauntlet selects and manages which DeFi markets a vault can deploy capital into. Includes risk tier classification and allocation management. See [Curation](/guides/concepts/curation). **Guardian** -- An off-chain agent (operated by Gauntlet) that submits operations to a vault contract, managing strategy allocations within on-chain constraints. See [Aera V3 Guardian Model](/guides/concepts/supported-protocols/aera-v3/guardian-model). **Hook** -- A smart contract module that validates guardian operations before execution, enforcing rules like permitted targets, approved calldata, and exposure limits. See [Aera V3 Hooks](/guides/concepts/supported-protocols/aera-v3/hooks). **LLTV (Liquidation Loan-to-Value)** -- Maximum ratio of borrowed value to collateral value before liquidation. For example, 86% LLTV means a borrower can borrow up to 86% of their collateral's value. A key parameter in Gauntlet's [curation](/guides/concepts/curation) evaluation. **MultiDepositorVault** -- A vault pooling capital from multiple depositors into shared liquidity. Depositors interact through a [Provisioner](#provisioner) and receive vault shares. See [Vault Types](/guides/concepts/vault-types). **Provisioner** -- A smart contract managing depositor interactions with a `MultiDepositorVault` -- deposits, redemptions, share pricing, and order solving. **Risk Tier** -- Classification of vaults by risk-return profile: **Prime** (conservative), **Balanced** (moderate), **Frontier** (higher yield). See [Curation](/guides/concepts/curation). **SingleDepositorVault** -- A vault dedicated to one entity. The owner deposits directly and a guardian manages allocations within hook constraints. See [Vault Types](/guides/concepts/vault-types). **Vault Share** -- A token representing proportional ownership in a [multi-depositor vault](/guides/concepts/vault-types). Value changes as the vault earns yield or incurs losses. # Security Source: https://docs.gauntlet.xyz/guides/concepts/security Audits, trust assumptions, and security properties for partner compliance and due diligence This page covers the security properties you can reference for compliance and due diligence. Aera, a Gauntlet product, uses audited smart contracts, constrained roles, and on-chain enforcement to limit risk. The protocol has had zero exploits since launch. ## Audits The Aera V3 protocol contracts powering Gauntlet vaults have been reviewed by multiple independent firms: | Auditor | Scope | Type | Notes | | ---------------- | ---------------------- | ----------------- | ---------------------------------------------------------------------------- | | **Spearbit** | Aera V3 core contracts | Audit (June 2025) | Comprehensive review of BaseVault, hooks, provisioner, and guardian patterns | | **OpenZeppelin** | Aera V3 contracts | Audit | Core vault and access control logic | | **Cantina** | Aera V3 contracts | Competitive audit | Community security competition with multiple independent reviewers | | **Immunefi** | Ongoing | Bug bounty | Active bounty program for responsible disclosure | Audit reports are published by the Aera protocol team. For the latest reports, see the [Aera security documentation](https://docs.aera.finance/the-protocol/security). ## Gauntlet Risk Management Aera is built and operated by Gauntlet, which has managed risk across 100+ DeFi protocols covering \$48B+ in digital assets. Gauntlet operates as the guardian for Aera vaults, bringing institutional-grade risk infrastructure to vault operations: * **Real-time monitoring** -- Gauntlet's risk systems continuously evaluate market conditions, protocol health, and portfolio exposures to inform guardian operations. * **On-chain enforcement** -- Risk constraints are enforced at the protocol level via the [guardian model](/guides/concepts/supported-protocols/aera-v3/guardian-model) and [hooks](/guides/concepts/supported-protocols/aera-v3/hooks). Constraint violations revert within the same transaction -- there is no delay between detection and enforcement. * **Curation methodology** -- Markets and protocols are evaluated against smart contract risk, liquidity risk, oracle risk, and counterparty risk before inclusion in any vault's allocation set. See [Curation](/guides/concepts/curation). ## Trust Assumptions | Participant | Trusts | Verified On-Chain | | --------------- | -------------------------------------------------------------------- | ------------------------------------------ | | **Depositor** | Vault owner to set safe constraints; guardian to operate competently | Hook validation, share accounting | | **Vault Owner** | Guardian to follow strategy; hooks to enforce rules | Hook execution, Merkle proof validation | | **Guardian** | Hooks to validate correctly; DeFi protocols to behave as expected | Operation execution through vault contract | **What is not trustless:** Strategy quality depends on Gauntlet's off-chain risk analysis. Guardian liveness is required for active management (depositors can still withdraw if a guardian goes offline). Merkle tree updates are a governance action, not automated. ## Circuit Breakers * **Pause** -- Vault owner or any guardian can halt all guardian operations within a single block, while preserving depositor withdrawals. No governance vote or timelock required. * **Hook-level guards** -- Individual hooks can reject operations when prices or exposures deviate beyond thresholds. Enforcement is synchronous -- violations revert the transaction before any state change. * **Multi-guardian isolation** -- Vaults support multiple guardians with independent permission sets. Compromising one guardian does not grant access to another's operations. For the full contract security model, see [Aera V3 Guardian Model](/guides/concepts/supported-protocols/aera-v3/guardian-model), [Aera V3 Hooks](/guides/concepts/supported-protocols/aera-v3/hooks), and [Contract Reference](/contract-reference/overview). # Aera V2 (Deprecated) Source: https://docs.gauntlet.xyz/guides/concepts/supported-protocols/aera-v2 Legacy vault protocol, now replaced by Aera V3 Aera V2 was the previous generation of the Aera vault protocol. It provided a simpler vault model with guardian-managed strategy execution, but lacked the modularity, hook system, and cross-chain capabilities introduced in V3. Aera V2 vaults are no longer being deployed, and the protocol is deprecated in favor of [Aera V3](/guides/concepts/supported-protocols/aera-v3/overview). If you are building new integrations or deploying new vaults, see the [Aera V3 Overview](/guides/concepts/supported-protocols/aera-v3/overview) for the current protocol architecture. ## Related Pages * [Aera V3 Overview](/guides/concepts/supported-protocols/aera-v3/overview) -- Current protocol architecture * [Vault Types](/guides/concepts/vault-types) -- Single-depositor and multi-depositor vault models # Aera V3 Cross-Chain Source: https://docs.gauntlet.xyz/guides/concepts/supported-protocols/aera-v3/cross-chain How Aera V3 enables multichain deposits and cross-chain yield deployment Gauntlet vaults support two distinct cross-chain capabilities: * **Multichain deposit** -- depositors enter from any supported chain, and capital pools into a single vault. Depositors on every chain receive the same yield regardless of where they deposited. * **Cross-chain yield deployment** -- the guardian bridges pooled capital to destination chains to access yield opportunities wherever they exist. These capabilities can be used independently or together. A vault like gtUSDa uses both: depositors enter from multiple chains, and the guardian deploys that pooled capital across chains to optimize yield. ## Multichain Deposit Some vaults accept deposits on multiple chains simultaneously. Each supported chain has its own `Provisioner` contract that handles local deposits and share accounting. Capital from all chains pools into a single vault, and all depositors -- regardless of which chain they deposited on -- share the same yield. This is how products like gtUSDa work: a depositor on Arbitrum and a depositor on Ethereum both hold shares in the same vault and earn the same returns. The Provisioner on each chain handles the local deposit experience natively -- depositors interact with contracts on their own chain and pay gas in their chain's native token. The guardian coordinates capital flows between chains to ensure the vault's total assets are accurately accounted for across all Provisioner instances. Share pricing reflects the aggregate value of all assets held across all chains. ## Cross-Chain Yield Deployment Independently of where deposits originate, the guardian can bridge vault capital to destination chains to execute DeFi strategies. Rather than requiring separate vaults on each chain, the Aera V3 protocol uses a provisioner model that coordinates capital movement between a vault's home chain and destination chains. This enables a single vault to hold assets on Ethereum while executing strategies on Arbitrum, Base, Optimism, or other supported networks -- all managed by the same guardian and governed by the same on-chain constraints. Cross-chain operations add complexity to vault management, but the Aera V3 architecture abstracts most of this complexity into the provisioner and bridging layer, keeping the core vault contracts and guardian model consistent across single-chain and multi-chain deployments. ## Provisioner Model The `Provisioner` contract is the entry point for depositors in multi-depositor vaults and the coordinator for cross-chain capital flows. It sits between depositors and the underlying vault, managing share accounting, deposit/redeem requests, and cross-chain entry and exit flows. Provisioners support two interaction models: **Synchronous deposits.** Depositors call `deposit` or `mint` directly on the Provisioner to receive vault units immediately. The Provisioner calculates the exchange rate using the `OracleRegistry` and mints units to the depositor. **Asynchronous requests.** Depositors call `requestDeposit` or `requestRedeem` to place an order. A solver (typically operated by the guardian) fulfills these requests in a later transaction, delivering vault units or underlying assets directly to the depositor's wallet. This async model is essential for cross-chain operations where bridging introduces latency. For multi-depositor vaults, the Provisioner manages the full lifecycle of vault units -- minting on deposit, burning on redemption, and tracking the total unit supply for fee and pricing calculations. ## CCTP Integration Cross-chain asset transfers in Gauntlet vaults use Circle's Cross-Chain Transfer Protocol (CCTP) for bridging USDC and other supported stablecoins between chains. CCTP provides native burn-and-mint bridging, meaning assets are burned on the source chain and natively minted on the destination chain -- there are no wrapped or synthetic tokens involved. CCTP integration fits into the guardian operation model naturally: the guardian submits a bridge operation as part of a normal operation batch, the operation hooks validate the bridge parameters against the Merkle tree, and the CCTP bridge executes the cross-chain transfer. The guardian then submits operations on the destination chain to deploy the bridged capital into DeFi strategies. ## Entry and Exit Flows Cross-chain vault operations follow structured entry and exit flows that coordinate between the home chain (where the vault and provisioner live) and destination chains (where DeFi strategies execute). **Entry flow (deposit and deploy):** 1. A depositor deposits assets into the Provisioner on the home chain 2. The Provisioner mints vault units and makes assets available to the vault 3. The guardian submits a bridge operation to transfer assets to the destination chain via CCTP 4. On the destination chain, the guardian submits strategy operations to deploy capital into DeFi protocols **Exit flow (withdraw and bridge back):** 1. The guardian submits operations on the destination chain to withdraw capital from DeFi protocols 2. The guardian bridges assets back to the home chain via CCTP 3. On the home chain, the vault holds the returned assets 4. Depositors redeem vault units through the Provisioner to receive underlying assets For asynchronous redemptions, the depositor submits a `requestRedeem` and the solver fulfills the request after the guardian has completed the exit flow and assets are available on the home chain. ## Supported Chains Gauntlet vaults operate across multiple EVM-compatible chains. The Aera V3 contracts are deployed on Ethereum, Arbitrum, Base, Optimism, Polygon, and Avalanche, with the same contract architecture and guardian model on each chain. A vault's home chain is where the primary vault and provisioner contracts live, and the guardian can bridge capital to any supported destination chain. Gauntlet also operates vaults on Solana through [GLAM](https://glam.systems/) (Gateway to Liquidity Asset Management), which provides SVM-native vault infrastructure. Solana strategies -- [Kamino](/guides/concepts/supported-protocols/kamino) lending and liquidity -- are managed under the same Gauntlet curation framework. Cross-chain provisioning enables EVM-based vaults to allocate capital to Solana strategies without requiring depositors to interact with Solana directly. The `BaseVault` contract can also be deployed as a sub-vault on a destination chain when a direct deposit/withdraw interface is not needed. In this configuration, the sub-vault on the destination chain handles local DeFi interactions while the home chain vault and provisioner manage the depositor-facing interface and cross-chain coordination. ## Related Pages * [Aera V3 Overview](/guides/concepts/supported-protocols/aera-v3/overview) -- Protocol architecture and contract overview * [Vault Types](/guides/concepts/vault-types) -- How cross-chain works with single and multi-depositor vaults * [Curation](/guides/concepts/curation) -- How Gauntlet manages cross-chain vault strategies * [Security](/guides/concepts/security) -- Trust assumptions for cross-chain operations # Aera V3 Guardian Model Source: https://docs.gauntlet.xyz/guides/concepts/supported-protocols/aera-v3/guardian-model How guardians manage vault operations in the Aera V3 protocol In the Aera V3 protocol, a guardian is the entity responsible for executing strategy operations on behalf of a vault. Gauntlet operates as the guardian for its vaults, submitting batches of operations -- swaps, protocol deposits, withdrawals, fee reports -- that are validated against on-chain constraints before execution. The guardian model is designed so that guardians have enough flexibility to execute complex DeFi strategies while being tightly constrained by the protocol to prevent unauthorized actions. This page explains how the guardian model works at the protocol level. For how Gauntlet uses the guardian role to curate and manage vaults, see [Curation](/guides/concepts/curation). For operational details on running a guardian, see the [Guardian Guides](/guides/guardian/single-depositor-vaults). ## Guardian Role Every Aera V3 vault has an owner and one or more guardians. The owner configures the vault -- setting guardian permissions, hook configurations, and pause controls -- while the guardian executes the day-to-day strategy operations. The separation is deliberate: the owner defines what actions are possible, and the guardian executes within those bounds. A vault can have **multiple guardians**, each with its own independent Merkle root defining a distinct set of allowed operations. This enables separation of concerns -- for example, one guardian handles yield strategy execution while another handles risk monitoring and can only pause or rebalance. Each guardian's permissions are scoped independently; compromising one guardian does not grant access to another's operation set. Guardians interact with vaults primarily through the `submit` function, which accepts a batch of operations to execute atomically. Each operation in the batch specifies a target contract, calldata, and optional parameters for chaining and callbacks. The protocol validates every operation against the guardian's Merkle tree before execution. The guardian can also call `pause` to halt all guardian operations on a vault -- an important safety mechanism that allows the guardian to immediately stop activity if a security concern arises. Any guardian on a vault can pause within a single block, without waiting for governance or multisig approval. Only the vault owner (or designated roles) can call `unpause` to resume operations. ## Operation Submission Guardian operations flow through a structured validation pipeline before any on-chain state changes occur. When a guardian calls `submit`, the following sequence executes: 1. **Submit hooks fire.** If the vault has `beforeSubmit` hooks configured, they run first. These can perform batch-level validation or accounting before any operations execute. 2. **Per-operation validation.** For each operation in the batch, the protocol checks the operation against the guardian's Merkle tree. The Merkle tree encodes which target contracts are allowed, which function selectors can be called, and what calldata constraints apply. Each operation includes a Merkle proof that the vault verifies on-chain. 3. **Operation hooks.** Individual operations can trigger pre-hooks (before execution) and post-hooks (after execution). Pre-hooks typically validate calldata parameters -- for example, enforcing slippage bounds on a swap by checking prices against the `OracleRegistry`. Post-hooks verify post-conditions like ensuring no residual token approvals remain. 4. **Execution.** The vault executes the validated call against the target DeFi protocol. 5. **Post-submit hooks.** After all operations complete, `afterSubmit` hooks run for batch-level post-processing -- fee accounting, position tracking, or state snapshots. Operations also support **chaining**, where the output of one operation feeds into the next. This enables multi-step DeFi interactions (approve then swap, deposit then stake) in a single atomic submission. Advanced operations can include native token transfers and register **callback listeners** for operations that trigger asynchronous responses. ## On-Chain Constraint Enforcement All guardian constraints are enforced on-chain at the protocol level -- not by off-chain monitoring, governance votes, or advisory frameworks. A guardian operation that violates any constraint reverts in the same transaction, within the same block. There is no window between detection and enforcement. The protocol enforces the following constraints on guardian actions: **Merkle tree whitelisting.** Every guardian is associated with a Merkle root that encodes the complete set of allowed operations. The vault owner sets this root via `setGuardianRoot`. A guardian cannot execute any operation that falls outside its Merkle tree -- there is no fallback or override. This means the set of contracts a guardian can interact with, the functions it can call, and the parameter ranges it can use are all cryptographically committed to on-chain. Changing the Merkle tree requires a new root to be set by the owner. **Hook-enforced invariants.** Pre-operation and post-operation hooks validate constraints in real time during execution. Hooks can enforce slippage bounds by checking prices against the `OracleRegistry`, enforce position concentration limits, validate that portfolio exposures remain within defined ranges, and reject operations that would move the vault outside its risk parameters. Because hooks execute within the same transaction as the guardian's operations, violations are caught and reverted before any state change takes effect. **Zero-approval enforcement.** Guardians cannot leave outgoing nonzero ERC-20 token approvals after an operation batch completes. This prevents a compromised guardian from pre-approving tokens for later extraction. Post-operation hooks enforce this constraint. **Whitelist validation.** Guardians must be registered on the vault's whitelist contract. Anyone can call `checkGuardianWhitelist` to verify whether a guardian is still authorized, and the protocol removes guardians that fail the whitelist check. **Pause capability.** Both the vault owner and any guardian can pause operations within a single block. This dual-pause model means a guardian can immediately halt activity if it detects anomalous conditions, without waiting for governance, multisig coordination, or timelock expiry. Only the owner can unpause. **Fee constraints.** For vaults using `FeeVault`, fee accrual is calculated by a dedicated fee calculator contract (`DelayedFeeCalculator` for single-depositor, `PriceAndFeeCalculator` for multi-depositor). The guardian or fee recipient can claim accrued fees, but cannot manipulate the fee calculation itself. ## Callbacks The Aera V3 protocol supports a callback mechanism that allows guardians to respond to on-chain events during operation execution. When an operation triggers an asynchronous response -- for example, a DeFi protocol that executes part of a request in a later transaction -- the guardian can register a callback listener as part of the operation submission. Callbacks enable complex multi-step interactions where the guardian needs to react to protocol-specific events. For example, a cross-chain bridge operation might require the guardian to finalize a transfer after a message relay confirms delivery on the destination chain. The callback system is part of the operation chaining infrastructure: when submitting an operation batch, the guardian can specify which operations should listen for callbacks and how the vault should handle the response. This keeps the guardian's interaction pattern flexible without requiring separate transactions for each step. Callback handling is an advanced topic relevant to guardian operators. For implementation details, see the [Guardian Guides](/guides/guardian/single-depositor-vaults). ## Related Pages * [Aera V3 Overview](/guides/concepts/supported-protocols/aera-v3/overview) -- Protocol architecture and contract overview * [Aera V3 Hooks](/guides/concepts/supported-protocols/aera-v3/hooks) -- How hooks validate and extend guardian operations * [Aera V3 Cross-Chain](/guides/concepts/supported-protocols/aera-v3/cross-chain) -- Multi-chain operations and provisioner flows * [Curation](/guides/concepts/curation) -- How Gauntlet uses the guardian role to curate vault strategies * [Guardian Guides](/guides/guardian/single-depositor-vaults) -- Operational guide for running a guardian # Aera V3 Hooks Source: https://docs.gauntlet.xyz/guides/concepts/supported-protocols/aera-v3/hooks How the hook system extends vault behavior in Aera V3 Hooks are extension points in the Aera V3 protocol that allow custom logic to run at specific moments during vault operations -- without modifying the core vault contracts. When a guardian submits operations, deposits occur, or tokens transfer, hooks can intercept these events to enforce constraints, calculate fees, validate parameters, or perform any other custom logic. This is how Gauntlet vaults enforce slippage limits, position boundaries, and transfer restrictions while keeping the underlying vault contracts generic and reusable. Hooks are configured by the vault owner and execute automatically as part of the vault's normal operation flow. The guardian does not choose which hooks run -- the hooks are part of the vault's on-chain configuration and apply uniformly to all operations. ## Hook Types Aera V3 defines several hook interfaces that cover different vault lifecycle points: **Submit hooks** run before and after an entire guardian submission batch. The vault owner configures these via `setSubmitHooks`, specifying a `beforeSubmit` and `afterSubmit` hook contract. Submit hooks operate on the full batch of operations and are useful for batch-level accounting, aggregate position checks, or state snapshots. **Operation hooks** run during individual operations within a submission batch. These are encoded in the guardian's Merkle tree alongside the operation itself: * **Pre-hooks** execute before the operation's target call. They typically validate calldata parameters -- for example, a Uniswap swap pre-hook might consult the `OracleRegistry` to enforce on-chain slippage bounds, ensuring the swap's minimum output is within an acceptable range of the oracle price. * **Post-hooks** execute after the operation completes. They verify post-conditions like ensuring no residual token approvals remain or confirming that expected token balances changed as intended. **Transfer hooks** (`IBeforeTransferHook`) run before token transfers within multi-depositor vaults. These can enforce transfer restrictions, blocklists, or compliance requirements on vault unit transfers between addresses. ## Execution Flow When a guardian submits a batch of operations to a Gauntlet vault, hooks execute at multiple points in the pipeline: The execution order is deterministic: 1. `beforeSubmit` hook fires once for the entire batch 2. For each operation: pre-hook, Merkle verification, execution, post-hook 3. `afterSubmit` hook fires once after all operations complete If any hook reverts, the entire submission transaction reverts. This ensures that hooks act as hard constraints -- a vault cannot bypass a failing hook check. ## Hook Composition Hooks in Aera V3 can be composed and chained together. A hook contract can internally delegate to other hook contracts, building a pipeline of validation and transformation logic. This composition pattern allows Gauntlet to layer multiple independent concerns into a single hook configuration: * A submit hook might first check aggregate position limits, then update a fee accounting snapshot, then emit events for off-chain monitoring -- each implemented as a separate internal hook in a composed pipeline. * An operation pre-hook might chain an oracle price check with a calldata parameter extraction step, where the first hook fetches the current price and the second validates that the operation's parameters are within bounds. The vault owner can also use **configurable hooks** -- hook contracts that accept configuration parameters (stored in the Merkle tree leaves) rather than having behavior hardcoded. This means the same hook contract can enforce different slippage bounds for different operations or different position limits for different assets, configured per-guardian through the Merkle tree. ## Common Use Cases Hooks enable a range of vault behaviors without requiring changes to the core Aera V3 contracts: **Slippage enforcement.** Pre-hooks on swap operations consult the `OracleRegistry` for current asset prices and verify that the swap's minimum output falls within acceptable bounds. This protects vault assets from sandwich attacks and excessive slippage during guardian-submitted trades. **Position limits.** Submit hooks or post-hooks can check that the vault's exposure to any single protocol, asset, or chain stays within defined limits after an operation batch completes. **Transfer restrictions.** Transfer hooks on multi-depositor vaults can enforce compliance rules, blocklists, or lock-up periods on vault unit transfers between addresses. **Fee calculation.** Post-submit hooks can snapshot vault state for fee calculation, ensuring that fee accrual reflects actual operations rather than being manipulated through timing. **Approval cleanup.** Post-hooks verify that no ERC-20 token approvals remain after operations complete, enforcing the zero-approval invariant that protects vault assets between guardian submissions. ## Related Pages * [Aera V3 Overview](/guides/concepts/supported-protocols/aera-v3/overview) -- Protocol architecture and contract overview * [Aera V3 Guardian Model](/guides/concepts/supported-protocols/aera-v3/guardian-model) -- How guardians submit operations validated by hooks * [Security](/guides/concepts/security) -- Trust assumptions and audit history * [Custom Hooks Guide](/guides/guardian/custom-hooks) -- Building and deploying custom hooks * [Contract Reference](/contract-reference/overview) -- Hook contract interfaces and ABIs # Aera V3 Source: https://docs.gauntlet.xyz/guides/concepts/supported-protocols/aera-v3/overview How Gauntlet integrates Aera V3 and what partners need to know ## What It Does [Aera V3](https://www.aera.finance/) is a noncustodial, trustless smart contract protocol for on-chain treasury and yield management. It provides the vault contracts, permission model, and extensibility framework that powers the majority of Gauntlet's vault infrastructure across Ethereum, Arbitrum, Base, Optimism, Polygon, and Avalanche. Aera V3 handles asset custody, operation validation, and hook-based extensibility while Gauntlet handles curation, strategy, and risk management. ## How Gauntlet Uses It Gauntlet deploys Aera V3 vaults in two configurations: * **Single-depositor vaults** serve dedicated treasuries where one entity deposits capital and Gauntlet manages strategy execution. These use `SingleDepositorVault` contracts with time-delayed fee calculation. * **Multi-depositor vaults** accept deposits from multiple participants through a `Provisioner` contract that handles minting, redeeming, and async order fulfillment. These use `MultiDepositorVault` contracts with unit-based fee calculation. Both configurations support a hook system for custom behavior at lifecycle points and a constrained guardian model where all operations are validated against on-chain Merkle trees. For architectural deep-dives on guardians, hooks, and cross-chain mechanics, see the [Aera V3 Architecture](/guides/concepts/supported-protocols/aera-v3/guardian-model) section in the Contracts tab. ## What Integrators Touch Aera V3 vaults expose the standard **ERC-4626 tokenized vault** interface. Integrators use the same `deposit`/`redeem` patterns described in the [Deposits and Withdrawals](/guides/developer/earn/deposits-and-withdrawals) guide. Key integration considerations: * **Vault type matters:** Single-depositor vaults have direct deposit/withdraw on the vault contract. Multi-depositor vaults route through a Provisioner with async fulfillment. See [Vault Types](/guides/concepts/vault-types). * **Fees:** Aera V3 includes a `FeeVault` contract for on-chain fee accrual and claiming. Partners can configure fee recipients. See [Fees](/integrate/fees). * **Wrapper vaults:** Partners needing bespoke allocations or custom fee configurations can use wrapper vaults that deposit into underlying Gauntlet vaults. See [Wrapper Vaults](/integrate/wrapper-vaults). * **Data:** Vault metrics are available through the Gauntlet API and on-chain. See [Displaying Data](/guides/developer/earn/displaying-data). ## Related Pages * [Deposits and Withdrawals](/guides/developer/earn/deposits-and-withdrawals) -- ERC-4626 deposit and withdrawal flows * [Vault Types](/guides/concepts/vault-types) -- Single-depositor and multi-depositor architectures * [Fees](/integrate/fees) -- Fee types, FeeVault integration, and fee recipient setup * [Deployed Vaults](/contract-reference/deployed-vaults) -- Aera V3 vault addresses by chain * [Contract Reference](/contract-reference/overview) -- Full contract documentation # Vault Types Source: https://docs.gauntlet.xyz/guides/concepts/vault-types Single-depositor and multi-depositor vaults -- which type fits your integration Gauntlet vaults come in two types. Which one you interact with depends on your integration pattern. ## Single-Depositor Vaults A single-depositor vault is dedicated to one entity -- typically an institution, DAO treasury, or protocol. The vault owner deposits capital directly and retains full control over deposits and withdrawals. * **One depositor, one vault** -- custom risk parameters tailored to the depositor's needs. * **Direct interaction** -- no share tokens or provisioner contracts; the owner deposits and withdraws directly. Single-depositor vaults suit entities that want dedicated, transparent vault management without sharing liquidity. ## Multi-Depositor Vaults A multi-depositor vault pools capital from many depositors into shared liquidity. Depositors receive **vault shares** representing their proportional ownership. * **Shared liquidity** -- multiple depositors contribute to and benefit from the same managed pool. * **Provisioner contract** -- depositors interact through a `Provisioner` that handles deposits, redemptions, and share pricing. * **Proportional returns** -- yield is distributed based on share ownership, net of fees. Multi-depositor vaults are the standard model for Gauntlet's public-facing vaults, including Morpho lending vaults. ## Risk Tiers Gauntlet classifies vaults into three risk tiers that reflect strategy aggressiveness: | Tier | Profile | Typical Allocation | | ------------ | -------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | **Prime** | Conservative. Blue-chip markets, deep liquidity, proven oracles. | Highest-LLTV Morpho markets, major collateral pairs | | **Balanced** | Moderate risk-reward. Broader market exposure with managed concentration limits. | Mix of established and mid-tier markets | | **Frontier** | Higher yield, higher risk. Newer protocols, less liquid markets. | Newer Morpho markets, alternative collateral, emerging protocols | Risk tier classification is part of Gauntlet's [curation methodology](/guides/concepts/curation). ## Protocol-Agnostic Architecture Aera V3's `BaseVault` is protocol-agnostic -- vaults are not tied to any single DeFi platform. The guardian's Merkle tree defines which protocols a vault can interact with, and the hook system enforces constraints regardless of the underlying protocol. This means a single vault can allocate across Morpho, Aave, Pendle, and other protocols simultaneously, and the vault owner can expand or change the protocol set without redeploying the vault. Gauntlet currently deploys vaults across: * **[Morpho](/guides/concepts/supported-protocols/morpho)** -- Isolated lending markets on Ethereum and Base. * **[Symbiotic](/guides/concepts/supported-protocols/symbiotic)** -- Restaking protocol. * **[Kamino](/guides/concepts/supported-protocols/kamino)** -- Lending protocol on Solana. New protocol integrations require adding operations to the guardian's Merkle tree and deploying any necessary hooks -- not changes to the core vault contracts. For protocol-level contract details, see [Aera V3 Overview](/guides/concepts/supported-protocols/aera-v3/overview). # Deposit Your First Dollar Source: https://docs.gauntlet.xyz/guides/developer/earn/deposits-and-withdrawals Deposit 1 USDC into gtUSDa on Base using the SDK, then confirm through the API. Deposit into gtUSDa on Base — a multi-depositor USDC vault on Aera V3. The same flow works for any Gauntlet vault. ## Initialize the SDK Set `builderCode` to your partner identifier — this enables attribution and fee sharing on every transaction the SDK builds. **Builder codes must be requested from Gauntlet** — using an unregistered string will append bytes to calldata but volume will not be counted. ```typescript theme={null} import { GauntletClient } from '@gauntlet-xyz/sdk' import { createPublicClient, createWalletClient, http, privateKeyToAccount } from 'viem' import { base } from 'viem/chains' const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const publicClient = createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!), }) const walletClient = createWalletClient({ account, chain: base, transport: http(process.env.RPC_URL_BASE!), }) const client = new GauntletClient({ evmClients: { [base.id]: publicClient }, wallet: walletClient, builderCode: 'your-builder-code', // issued by Gauntlet — request from the partnerships team }) ``` ## Build the Deposit `VaultId.AeraUsdAlpha` is a typed constant from the SDK — its value is `'gtusda'`, the vault's identifier in the manifest. You can also discover vault IDs at runtime using `getVaults`. ```typescript theme={null} import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm' const steps = await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, receiver: '0xReceiver', // optional, defaults to wallet account }) // returns: // [ // { payload: { type: 'approve', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } }, // { payload: { type: 'requestDeposit', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } } // ] ``` The SDK resolves gtUSDa on Base to vault `0x000000000001CdB57E58Fa75Fe420a0f4D6640D5` and provisioner `0x18CF8d963E1a727F9bbF3AEffa0Bd04FB4dBdA07`, checks USDC allowance on-chain, and builds the deposit with attribution baked into calldata. ## Submit the Transactions Each step exposes two surfaces. Pick the one that fits your stack — both carry attribution. ### `step.payload` — scripts and embedded wallets `payload.data` is the fully formed calldata with attribution already concatenated. Use `estimateGas` before sending — it simulates the exact bytes that will hit the chain (catching reverts before you spend gas) and returns the gas units needed. Wait for each receipt before the next step, since the deposit reverts if the approval hasn't landed yet. ```typescript theme={null} // steps = [approve, requestDeposit] — must execute in order for (const step of steps) { // Estimate gas — simulates the exact calldata that will be broadcast. // Throws if the call would revert (e.g. insufficient balance, wrong allowance) // before spending gas. Returns the gas units needed for the tx. const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) // Send with the estimated gas limit — prevents out-of-gas failures on-chain. const hash = await walletClient.sendTransaction({ ...step.payload, gas }) // Wait for confirmation before the next step — the deposit will revert if the // approval isn't mined first. const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } ``` ### `step.tx` — browser wallets (wagmi) Use the structured ABI fields with `writeContract`. Pass `step.tx.attribution` as `dataSuffix` — wagmi appends it to the calldata before sending. Without it the transaction goes through but volume is not attributed. ```typescript theme={null} // wagmi — MetaMask, Coinbase Wallet, WalletConnect, etc. for (const step of steps) { await walletClient.writeContract({ address: step.tx.address, abi: step.tx.abi, functionName: step.tx.functionName, args: step.tx.args, account: step.tx.account, dataSuffix: step.tx.attribution, // required — omitting silently drops attribution }) } ``` ## Confirm the Deposit ```typescript theme={null} import { getUserCurrentBalance } from '@gauntlet-xyz/sdk' import { VaultId } from '@gauntlet-xyz/sdk/evm' const balance = await getUserCurrentBalance(client, { vaultId: VaultId.AeraUsdAlpha, address: '0xUser', }) // returns: // [{ // chain: 'base', // token: '0xtoken', // decimals: 6, // pendingDeposit: 1_000_000n, // locked in provisioner, not yet earning yield — moves to `balance` once solver settles (~2 hours) // balance: 0n, // pendingWithdraw: 0n, // }] ``` ## Withdraw from gtUSDa Same pattern — build steps, then submit using whichever path matches your stack. The optional `receiver` designates the address that receives the withdrawn assets. Vault shares are always burned from the connected wallet; setting `receiver` only redirects where the assets land. When omitted, assets go to the wallet account. ```typescript theme={null} import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm' const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, entireAmount: true, receiver: '0xReceiver', // optional; assets are sent here instead of the wallet account }) // returns: // [ // { payload: { type: 'requestRedeem', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } } // ] // step.payload path for (const step of steps) { const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const hash = await walletClient.sendTransaction({ ...step.payload, gas }) const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') throw new Error(`Transaction reverted: ${step.payload.type}`) } ``` Other withdraw variants: ```typescript theme={null} // By shares const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, shares: 500_000000000000000000n, }) // By asset amount const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 500_000n, }) ``` On Aera vaults, a sync deposit locks all of the depositor's vault units for 1 hour (the vault's deposit refund timeout). Withdrawals of either mode during that window revert on-chain with `Aera__UnitsLocked`. ## What's Next Show the user's gtUSDa position, ROI, and activity history. Inspect deposit volume and attribution data for your integration. How ERC-8021 builder codes work and how to verify attribution is tracked. Full constructor, data methods, transaction methods, and error reference. # Discover Top-Performing Vaults Source: https://docs.gauntlet.xyz/guides/developer/earn/displaying-data Use the SDK or API to find, filter, and compare Gauntlet vaults — shown with gtUSDa on Base. Find vaults that fit your product, compare their performance, and decide what to offer users. ## Initialize ```typescript SDK theme={null} import { GauntletClient } from '@gauntlet-xyz/sdk' import { createPublicClient, http } from 'viem' import { base } from 'viem/chains' const client = new GauntletClient({ evmClients: { [base.id]: createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!) }), }, }) ``` ```typescript API theme={null} const API_BASE_URL = 'https://api.gauntlet.xyz' const API_KEY = process.env.GAUNTLET_API_KEY! async function apiGet(path: string) { const resp = await fetch(`${API_BASE_URL}${path}`, { headers: { Authorization: `Bearer ${API_KEY}` }, }) if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`) return resp.json() } ``` ## List and Filter Vaults ```typescript SDK theme={null} import { getVaults } from '@gauntlet-xyz/sdk/evm' const candidates = await getVaults(client, { chainId: base.id }) // returns: // [ // { // vaultId: "gtusda", // SDK manifest slug — short identifier used by SDK methods // name: "gtUSDa", // protocol: "aera", // strategy: "...", // deployments: [ // { // chain: "evm", // chainId: 8453, // vaultAddress: "0x...", // vaultType: "multi-depositor", // depositMode: "async", // supplyToken: [{ symbol: "USDC", address: "0x...", decimals: 6 }] // } // ] // }, // ... // ] ``` ```typescript API theme={null} const { data: vaults } = await apiGet( '/v1/vaults?chain=base&strategy=lending&risk_tier=prime&limit=50' ) // returns: // [ // { // name: "gtUSDa", // metrics: { // apy_net: "8.2", // tvl_usd: "45000000" // } // }, // ... // ] ``` The SDK `getVaults` function reads from the bundled vault manifest — no network request. For live metrics such as APY and TVL, use the API. ## Get Vault Detail — gtUSDa The API identifies vaults by a `chain_id:address` string (e.g. gtUSDa on Base is `8453:0x000000000001cdb57e58fa75fe420a0f4d6640d5`). Use a deployment's chain ID and vault address from `/v1/vaults` or the SDK manifest. ```typescript API theme={null} const GTUSDA_BASE = '8453:0x000000000001cdb57e58fa75fe420a0f4d6640d5' const [{ data: detail }, { data: latest }] = await Promise.all([ apiGet(`/v1/vaults/${GTUSDA_BASE}`), apiGet(`/v1/vaults/${GTUSDA_BASE}/latest`), ]) // detail → { // name: "gtUSDa", // deployments: [ // { chain: "base", address: "0x000000000001CdB57E58Fa75Fe420a0f4D6640D5" } // ] // } // latest → { // metrics: { // apy: { value: "8.2" }, // tvl_usd: { value: "45000000" }, // share_price: { value: "1.032" } // } // } ``` ## Chart Historical Performance ```typescript API theme={null} const { data: points } = await apiGet( `/v1/vaults/${GTUSDA_BASE}/timeseries?granularity=day&start=2026-01-01` ) // returns: // [ // { timestamp: "2026-01-01", apy: "7.9", tvl_usd: "42000000" }, // { timestamp: "2026-01-02", apy: "8.0", tvl_usd: "42500000" }, // ... // ] ``` ## What's Next Once you've picked gtUSDa (or another vault), move to the deposit flow. Use the SDK to deposit 1,000 USDC into gtUSDa on Base. Full vault listing, detail, metrics, timeseries, and events endpoints. # Show User's Return Source: https://docs.gauntlet.xyz/guides/developer/earn/show-users-return Show a user's gtUSDa position, ROI over time, and recent activity using the SDK or API. After a user deposits into gtUSDa, show them what they're earning. ## Load Current Position ```typescript SDK theme={null} import { getUserCurrentBalance } from '@gauntlet-xyz/sdk' import { VaultId } from '@gauntlet-xyz/sdk/evm' const balances = await getUserCurrentBalance(client, { vaultId: VaultId.AeraUsdAlpha, address: '0xUser', }) console.log(balances) // [ // { // chain: 'base', // token: '0xUSDCADDRESS123', // decimals: 6, // pendingDeposit: 0n, // balance: 1_000_000n, // pendingWithdraw: 0n, // }, // ] ``` ```typescript API theme={null} // API vault IDs use chain_id:address (gtUSDa on Base shown here). const GTUSDA_BASE = '8453:0x000000000001cdb57e58fa75fe420a0f4d6640d5' const { data: positions, meta } = await apiGet( '/v1/users/0xUserWallet/positions/latest' ) // meta.summary returns: // { // total_value_usd: "12500.00", // portfolio_apy: "7.8" // } const gtusda = positions.find(p => p.vault_id === GTUSDA_BASE) // returns: // { // vault_id: "8453:0x000000000001cdb57e58fa75fe420a0f4d6640d5", // shares: "968000000000000000000", // asset_value: "1032.00", // roi_pct: "3.2" // } ``` ## Chart Return Over Time ```typescript API theme={null} const { data: portfolioCurve } = await apiGet( '/v1/users/0xUserWallet/positions/timeseries?granularity=day&start=2026-01-01' ) // returns: // [ // { timestamp: "2026-01-01", value_usd: "10000.00", roi_usd: "0" }, // { timestamp: "2026-02-01", value_usd: "10800.00", roi_usd: "800.00" }, // ... // ] const { data: vaultCurve } = await apiGet( `/v1/users/0xUserWallet/positions/${GTUSDA_BASE}/timeseries?granularity=day&start=2026-01-01` ) // returns: // [ // { timestamp: "2026-01-01", value_usd: "1000.00", roi_usd: "0" }, // { timestamp: "2026-03-20", value_usd: "1032.00", roi_usd: "32.00" } // ] ``` ## Show Recent Activity ```typescript API theme={null} const { data: txns } = await apiGet( `/v1/users/0xUserWallet/positions/${GTUSDA_BASE}/transactions?limit=20` ) // returns: // [ // { // type: "deposit", // vault_id: "8453:0x000000000001cdb57e58fa75fe420a0f4d6640d5", // amount: "1000.00", // timestamp: "2026-01-15T10:30:00Z" // }, // ... // ] ``` ## What's Next Monitor deposit volume and partner attribution across your integration. Full user positions, timeseries, and transactions endpoints. # Custom Hooks Source: https://docs.gauntlet.xyz/guides/guardian/custom-hooks Building and deploying custom hooks for Aera V3 vaults Hooks extend vault behavior at specific lifecycle points without modifying core contracts. For background on how the hook system works, see the [Hooks concept page](/guides/concepts/supported-protocols/aera-v3/hooks). This guide covers building hooks: understanding the interfaces, composing them, and implementing one end-to-end. Before starting, make sure you have: * [Foundry](https://book.getfoundry.sh/) installed (`forge`, `cast`, `anvil`) * Familiarity with Solidity development and testing * Understanding of the [Aera V3 Hooks](/guides/concepts/supported-protocols/aera-v3/hooks) concept page ## Hook Interfaces Aera V3 defines six hook interfaces that fire at different points in the vault lifecycle. Each interface is a single function that the hook contract must implement. | Interface | Function | When It Fires | | ---------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `IBeforeSubmitHook` | `beforeSubmit(Operation[])` | Before the entire guardian submission batch executes. Used for batch-level validation. | | `IAfterSubmitHook` | `afterSubmit(Operation[])` | After all operations in the batch complete. Used for post-batch accounting and state snapshots. | | `IBeforeOperationHook` | `beforeOperation(address, bytes, bytes)` | Before each individual operation's target call. Used for calldata validation (e.g., slippage checks). | | `IAfterOperationHook` | `afterOperation(address, bytes, bytes)` | After each individual operation completes. Used for post-condition checks (e.g., approval cleanup). | | `IBeforeTransferHook` | `beforeTransfer(address, address, uint256)` | Before vault unit transfers in multi-depositor vaults. Used for access control and compliance. | | `IBeforeClaimHook` | `beforeClaim(address, address, uint256)` | Before fee claim operations. Used for claim validation and authorization. | For full function signatures, parameter types, and detailed documentation, see the [Contract Reference: Hooks](/contract-reference/hooks). ## Hook Lifecycle When a guardian submits a batch of operations, hooks execute in a deterministic order: 1. **`beforeSubmit`** fires once for the entire batch 2. For each operation in the batch: * **`beforeOperation`** fires with the operation's target, calldata, and hook data * The operation executes against the target contract * **`afterOperation`** fires with the same parameters 3. **`afterSubmit`** fires once after all operations complete Transfer hooks and claim hooks fire independently of the submission flow: * **`beforeTransfer`** fires during ERC-20 transfer calls on multi-depositor vault units * **`beforeClaim`** fires during fee claim operations If any hook reverts, the entire transaction reverts. Hooks act as hard constraints -- the vault cannot bypass a failing hook. The [Hooks concept page](/guides/concepts/supported-protocols/aera-v3/hooks) includes a detailed breakdown of execution flow with examples of common hook configurations. ## Composition Patterns **Chaining hooks.** A hook contract can internally delegate to other hook contracts, building a pipeline of validation logic. For example, a submit hook might first check aggregate position limits, then update a fee accounting snapshot, then emit events for off-chain monitoring -- each concern implemented as a separate internal hook. The vault owner configures hooks via vault settings, and the vault calls the configured hook contract at each lifecycle point. **Configurable hooks.** Hook contracts can accept configuration parameters rather than having behavior hardcoded. Operation hooks receive `hookData` from the Merkle tree, which means the same hook contract can enforce different slippage bounds for different operations or different position limits for different assets. This is configured per-guardian through the Merkle tree leaves, not by modifying the hook contract itself. For more on composition patterns, see [Hooks: Hook Composition](/guides/concepts/supported-protocols/aera-v3/hooks#hook-composition). ## Tutorial: Building a Transfer Restriction Hook This tutorial builds a complete hook from scratch: a transfer restriction hook that only allows vault unit transfers to allowlisted addresses. This is a common compliance requirement for multi-depositor vaults where vault shares should only be held by approved counterparties. ### Overview The hook implements `IBeforeTransferHook`. When a vault unit transfer occurs, the hook checks whether the recipient is on an allowlist. If not, the transfer reverts. The hook owner can add and remove addresses from the allowlist at any time. ### Step 1: Project Setup Create a new Foundry project and set up the directory structure: ```bash theme={null} forge init transfer-restriction-hook cd transfer-restriction-hook ``` Create the hook interface file. This mirrors the `IBeforeTransferHook` interface from the Aera V3 protocol: ```solidity theme={null} // src/IBeforeTransferHook.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; interface IBeforeTransferHook { function beforeTransfer(address from, address to, uint256 amount) external; } ``` ### Step 2: Implement the Hook Create the transfer restriction hook contract: ```solidity theme={null} // src/TransferRestrictionHook.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {IBeforeTransferHook} from "./IBeforeTransferHook.sol"; /// @title TransferRestrictionHook /// @notice Restricts vault unit transfers to allowlisted addresses. /// @dev Implements IBeforeTransferHook. The vault calls beforeTransfer /// on every unit transfer. If the recipient is not allowlisted, /// the transfer reverts. contract TransferRestrictionHook is IBeforeTransferHook { address public owner; mapping(address => bool) public allowlisted; error NotOwner(); error RecipientNotAllowlisted(address to); event AddressAllowlisted(address indexed account); event AddressRemovedFromAllowlist(address indexed account); modifier onlyOwner() { if (msg.sender != owner) revert NotOwner(); _; } constructor(address _owner) { owner = _owner; } /// @notice Called by the vault before every unit transfer. /// @dev Reverts if the recipient is not on the allowlist. /// The `from` and `amount` parameters are available for /// more complex restrictions but unused in this example. function beforeTransfer(address, address to, uint256) external view { if (!allowlisted[to]) revert RecipientNotAllowlisted(to); } /// @notice Add an address to the transfer allowlist. function addToAllowlist(address account) external onlyOwner { allowlisted[account] = true; emit AddressAllowlisted(account); } /// @notice Remove an address from the transfer allowlist. function removeFromAllowlist(address account) external onlyOwner { allowlisted[account] = false; emit AddressRemovedFromAllowlist(account); } } ``` The contract is deliberately simple: an owner, an allowlist mapping, and the `beforeTransfer` function that reverts if the recipient is not allowlisted. In production, you might add batch operations, timelocks, or role-based access -- but the core pattern stays the same. ### Step 3: Write Tests Create a test file that verifies the hook behaves correctly: ```solidity theme={null} // test/TransferRestrictionHook.t.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {Test} from "forge-std/Test.sol"; import {TransferRestrictionHook} from "../src/TransferRestrictionHook.sol"; contract TransferRestrictionHookTest is Test { TransferRestrictionHook hook; address owner = address(this); address alice = address(0xA11CE); address bob = address(0xB0B); function setUp() public { hook = new TransferRestrictionHook(owner); } function test_allowlistedTransferSucceeds() public { hook.addToAllowlist(alice); // Should not revert -- alice is allowlisted hook.beforeTransfer(bob, alice, 100); } function test_disallowedTransferReverts() public { // Bob is not allowlisted -- transfer should revert vm.expectRevert( abi.encodeWithSelector( TransferRestrictionHook.RecipientNotAllowlisted.selector, bob ) ); hook.beforeTransfer(alice, bob, 100); } function test_ownerCanUpdateAllowlist() public { hook.addToAllowlist(alice); assertTrue(hook.allowlisted(alice)); hook.removeFromAllowlist(alice); assertFalse(hook.allowlisted(alice)); } function test_nonOwnerCannotUpdateAllowlist() public { vm.prank(alice); vm.expectRevert(TransferRestrictionHook.NotOwner.selector); hook.addToAllowlist(bob); } } ``` ### Step 4: Run Tests Run the test suite with verbose output to see individual test results: ```bash theme={null} forge test -vv ``` Expected output: ``` [PASS] test_allowlistedTransferSucceeds() (gas: ...) [PASS] test_disallowedTransferReverts() (gas: ...) [PASS] test_ownerCanUpdateAllowlist() (gas: ...) [PASS] test_nonOwnerCannotUpdateAllowlist() (gas: ...) ``` All four tests should pass: allowlisted transfers succeed, disallowed transfers revert with `RecipientNotAllowlisted`, the owner can add and remove addresses, and non-owners cannot modify the allowlist. ### Step 5: Deploy Create a deployment script: ```solidity theme={null} // script/DeployHook.s.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {TransferRestrictionHook} from "../src/TransferRestrictionHook.sol"; contract DeployHook is Script { function run() external { uint256 deployerKey = vm.envUint("PRIVATE_KEY"); address hookOwner = vm.envAddress("HOOK_OWNER"); vm.startBroadcast(deployerKey); TransferRestrictionHook hook = new TransferRestrictionHook(hookOwner); vm.stopBroadcast(); } } ``` Deploy with Forge: ```bash theme={null} forge script script/DeployHook.s.sol:DeployHook \ --rpc-url $RPC_URL \ --broadcast \ --verify ``` After deploying, the vault owner must configure the hook on the target vault. Hook registration is an owner operation, not a guardian operation. The owner calls `setBeforeTransferHook` on the multi-depositor vault, passing the deployed hook's address. See the [Multi-Depositor Vault contract reference](/contract-reference/multi-depositor-vault) for details. ## Related Pages * [Aera V3 Hooks](/guides/concepts/supported-protocols/aera-v3/hooks) -- Conceptual overview of the hook system * [Contract Reference: Hooks](/contract-reference/hooks) -- Full interface documentation for all hook types * [Single-Depositor Vaults](/guides/guardian/single-depositor-vaults) -- Guardian operations for single-depositor vaults * [Multi-Depositor Vaults](/guides/guardian/multi-depositor-vaults) -- Guardian operations including transfer hooks # Multi-Depositor Vaults Source: https://docs.gauntlet.xyz/guides/guardian/multi-depositor-vaults Operating multi-depositor vaults as a guardian -- order solving, price reporting, fee reporting, transfer hooks, and emergency procedures Multi-depositor vaults extend the base guardian workflow with shared liquidity across multiple depositors, tokenized vault units (ERC-20), and an async order-solving model for deposits and redemptions. For background on how the guardian role works at the protocol level, see the [Guardian Model](/guides/concepts/supported-protocols/aera-v3/guardian-model). For base guardian operations like strategy execution and vault reads, see the [Single-Depositor Vaults](/guides/guardian/single-depositor-vaults) guide -- this page focuses on the operations unique to multi-depositor vaults. Before starting, make sure you have: * A wallet with guardian permissions on the target vault * [viem](https://viem.sh) 2.x installed (`npm install viem`) or [Foundry](https://book.getfoundry.sh/) for the `cast` CLI * Familiarity with the [Aera V3 Guardian Model](/guides/concepts/supported-protocols/aera-v3/guardian-model) * Understanding of the [Provisioner](/contract-reference/provisioner) model for depositor interactions (see [Cross-Chain](/guides/concepts/supported-protocols/aera-v3/cross-chain) for conceptual overview) ## Setup Configure your environment for interacting with the vault, Provisioner, and PriceAndFeeCalculator contracts. Multi-depositor vaults require more contract addresses than single-depositor vaults because pricing and order fulfillment involve additional periphery contracts. ```typescript viem theme={null} 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!), }) // Contract addresses -- see Contract Reference for deployed addresses // https://docs.gauntlet.xyz/contract-reference/addresses const VAULT_ADDRESS = '0x...' as const const PROVISIONER_ADDRESS = '0x...' as const const PRICE_FEE_CALCULATOR_ADDRESS = '0x...' as const const FEE_VAULT_ADDRESS = '0x...' as const ``` ```bash cast theme={null} # Set environment variables for all subsequent commands export RPC_URL_ETHEREUM="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY" export PRIVATE_KEY="0xYOUR_PRIVATE_KEY" # Contract addresses -- see Contract Reference for deployed addresses # https://docs.gauntlet.xyz/contract-reference/addresses export VAULT_ADDRESS="0x..." export PROVISIONER_ADDRESS="0x..." export PRICE_FEE_CALCULATOR_ADDRESS="0x..." export FEE_VAULT_ADDRESS="0x..." ``` ## Strategy Execution Strategy execution in multi-depositor vaults uses the same `submit(Operation[])` pattern as single-depositor vaults -- the guardian submits batched operations that are validated against a Merkle tree. See the [Single-Depositor Vaults](/guides/guardian/single-depositor-vaults#strategy-execution) guide for full details on operation submission, vault state reads, and rebalancing. ```typescript viem theme={null} // 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 in an MDV context 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) await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 2 }) ``` ```bash cast theme={null} # Submit a single operation -- complex struct submissions are typically scripted cast send $VAULT_ADDRESS \ "submit((address,bytes,uint256,bytes32[],address,bytes,address,bytes)[])" \ "[($TARGET,$CALLDATA,0,[],0x0000000000000000000000000000000000000000,0x,0x0000000000000000000000000000000000000000,0x)]" \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ## Order Solving Order solving is the primary difference between multi-depositor and single-depositor vault operations. Depositors interact with the vault through the [Provisioner](/contract-reference/provisioner) contract using an asynchronous request/fulfill lifecycle. The guardian (acting as solver) monitors pending requests and fulfills them through the vault's submit flow. ### How Orders Work The async order lifecycle has three stages: 1. **Request** -- A depositor calls `requestDeposit` or `requestRedeem` on the Provisioner, locking their assets (for deposits) or vault units (for redemptions). Each request receives a unique `requestId`. 2. **Fulfill** -- The guardian processes the request through the vault's submit flow. For deposit requests, the guardian ensures assets are accounted for and vault units are allocated. For redemption requests, the guardian ensures exit positions are unwound and assets are available. 3. **Claim** -- The depositor (or receiver) calls `claimDeposit` or `claimRedeem` on the Provisioner to receive their vault units or underlying assets. This async model is essential for cross-chain operations where CCTP bridging introduces latency between the request and fulfillment. See [Cross-Chain](/guides/concepts/supported-protocols/aera-v3/cross-chain) for the conceptual overview of cross-chain vault flows. ### Monitoring Pending Orders Monitor the Provisioner for incoming deposit and redemption requests by watching for `DepositRequested` and `RedeemRequested` events. This lets the guardian detect new orders that need fulfillment. ```typescript viem theme={null} const provisionerEventAbi = [ { name: 'DepositRequested', type: 'event', inputs: [ { name: 'requestId', type: 'uint256', indexed: true }, { name: 'owner', type: 'address', indexed: true }, { name: 'assets', type: 'uint256', indexed: false }, ], }, { name: 'RedeemRequested', type: 'event', inputs: [ { name: 'requestId', type: 'uint256', indexed: true }, { name: 'owner', type: 'address', indexed: true }, { name: 'shares', type: 'uint256', indexed: false }, ], }, ] as const // Watch for new deposit requests const depositLogs = await publicClient.getLogs({ address: PROVISIONER_ADDRESS, event: provisionerEventAbi[0], fromBlock: 'latest', }) for (const log of depositLogs) { console.log('Pending deposit:', { requestId: log.args.requestId, owner: log.args.owner, assets: log.args.assets, }) } // Watch for new redemption requests const redeemLogs = await publicClient.getLogs({ address: PROVISIONER_ADDRESS, event: provisionerEventAbi[1], fromBlock: 'latest', }) for (const log of redeemLogs) { console.log('Pending redemption:', { requestId: log.args.requestId, owner: log.args.owner, shares: log.args.shares, }) } ``` ```bash cast theme={null} # Query recent deposit request events cast logs --address $PROVISIONER_ADDRESS \ "DepositRequested(uint256 indexed requestId, address indexed owner, uint256 assets)" \ --from-block latest \ --rpc-url $RPC_URL # Query recent redemption request events cast logs --address $PROVISIONER_ADDRESS \ "RedeemRequested(uint256 indexed requestId, address indexed owner, uint256 shares)" \ --from-block latest \ --rpc-url $RPC_URL ``` ### Fulfilling Deposit Orders When a depositor places a deposit request via `requestDeposit`, the guardian fulfills it by processing the request through the vault's submit flow. Once fulfilled, the depositor can claim their vault units by calling `claimDeposit` on the Provisioner. ```typescript viem theme={null} const provisionerAbi = [ { name: 'claimDeposit', type: 'function', stateMutability: 'nonpayable', inputs: [ { name: 'requestId', type: 'uint256' }, { name: 'receiver', type: 'address' }, ], outputs: [{ type: 'uint256' }], }, ] as const // After the guardian fulfills the request via submit, the depositor claims: const requestId = 1n // The request ID from DepositRequested event const receiver = '0x...' as `0x${string}` // Depositor's address const { request: claimReq } = await publicClient.simulateContract({ address: PROVISIONER_ADDRESS, abi: provisionerAbi, functionName: 'claimDeposit', args: [requestId, receiver], account, }) const claimTx = await walletClient.writeContract(claimReq) const receipt = await publicClient.waitForTransactionReceipt({ hash: claimTx, confirmations: 2 }) console.log('Deposit claimed, vault units received:', receipt.transactionHash) ``` ```bash cast theme={null} # Claim vault units from a fulfilled deposit request # requestId: the ID from the DepositRequested event cast send $PROVISIONER_ADDRESS \ "claimDeposit(uint256,address)(uint256)" \ 1 \ $RECEIVER_ADDRESS \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ### Fulfilling Redemption Orders Redemption requests follow the same async pattern. The depositor calls `requestRedeem` on the Provisioner, and after the guardian ensures the underlying assets are available (by unwinding positions if needed via submit), the depositor claims the assets with `claimRedeem`. ```typescript viem theme={null} const redeemClaimAbi = [ { name: 'claimRedeem', type: 'function', stateMutability: 'nonpayable', inputs: [ { name: 'requestId', type: 'uint256' }, { name: 'receiver', type: 'address' }, ], outputs: [{ type: 'uint256' }], }, ] as const // After the guardian fulfills the redemption via submit, the depositor claims: const redeemRequestId = 2n // The request ID from RedeemRequested event const assetReceiver = '0x...' as `0x${string}` const { request: redeemReq } = await publicClient.simulateContract({ address: PROVISIONER_ADDRESS, abi: redeemClaimAbi, functionName: 'claimRedeem', args: [redeemRequestId, assetReceiver], account, }) const redeemTx = await walletClient.writeContract(redeemReq) const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemTx, confirmations: 2 }) console.log('Redemption claimed, assets received:', redeemReceipt.transactionHash) ``` ```bash cast theme={null} # Claim assets from a fulfilled redemption request cast send $PROVISIONER_ADDRESS \ "claimRedeem(uint256,address)(uint256)" \ 2 \ $RECEIVER_ADDRESS \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` Order fulfillment is executed through the guardian's `submit` flow on the vault. The exact fulfillment mechanics depend on whether the deposit/redemption is same-chain or cross-chain. See the [Provisioner Contract Reference](/contract-reference/provisioner) for full function signatures and the [Cross-Chain](/guides/concepts/supported-protocols/aera-v3/cross-chain) page for how cross-chain bridging integrates with the order lifecycle. ## Price Reporting Price reporting is unique to multi-depositor vaults. Because multiple depositors share the same pool, accurate unit pricing is essential for fair minting and redemption of vault shares. The `PriceAndFeeCalculator` contract manages price snapshots and uses them for both share pricing and fee computation. ### Why Prices Matter In a multi-depositor vault, each depositor's ownership is represented by ERC-20 vault units. The unit price determines how many shares a depositor receives on deposit and how many assets they receive on redemption. Accurate, timely price reporting ensures: * Fair entry and exit prices for all depositors * Correct NAV (Net Asset Value) calculation for the vault * Accurate fee accrual based on vault performance The `PriceAndFeeCalculator` uses managed accountant snapshots rather than real-time pricing to prevent fee manipulation through short-term vault value changes. See the [Periphery Contract Reference](/contract-reference/periphery) for full function signatures. ### Taking Snapshots The guardian takes price snapshots by calling `snapshot` on the `PriceAndFeeCalculator`. This records the current unit price and total supply for fee calculation. Snapshots are typically taken as part of the submit flow via an `afterSubmit` hook, but can also be called directly. ```typescript viem theme={null} const priceCalcAbi = [ { name: 'snapshot', type: 'function', stateMutability: 'nonpayable', inputs: [ { name: 'unitPrice', type: 'uint256' }, { name: 'totalSupply', type: 'uint256' }, ], outputs: [], }, ] as const // Current unit price and total supply -- derived from vault state const unitPrice = 1050000000000000000n // 1.05 in 18 decimals (vault has appreciated 5%) const totalSupply = 10000000000000000000000n // 10,000 vault units const { request: snapshotReq } = await publicClient.simulateContract({ address: PRICE_FEE_CALCULATOR_ADDRESS, abi: priceCalcAbi, functionName: 'snapshot', args: [unitPrice, totalSupply], account, }) const snapshotTx = await walletClient.writeContract(snapshotReq) await publicClient.waitForTransactionReceipt({ hash: snapshotTx, confirmations: 2 }) console.log('Price snapshot taken:', snapshotTx) ``` ```bash cast theme={null} # Take a price snapshot with current unit price and total supply # unitPrice: 1.05 in 18 decimals, totalSupply: 10,000 units in 18 decimals cast send $PRICE_FEE_CALCULATOR_ADDRESS \ "snapshot(uint256,uint256)" \ 1050000000000000000 \ 10000000000000000000000 \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ### Monitoring Prices Read the current vault state to determine the unit price and total supply before taking a snapshot or verifying pricing accuracy. ```typescript viem theme={null} const vaultPriceAbi = [ { name: 'totalAssets', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }], }, { name: 'totalSupply', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }], }, { name: 'convertToShares', type: 'function', stateMutability: 'view', inputs: [{ name: 'assets', type: 'uint256' }], outputs: [{ type: 'uint256' }], }, { name: 'convertToAssets', type: 'function', stateMutability: 'view', inputs: [{ name: 'shares', type: 'uint256' }], outputs: [{ type: 'uint256' }], }, ] as const const [totalAssets, totalSupply] = await Promise.all([ publicClient.readContract({ address: VAULT_ADDRESS, abi: vaultPriceAbi, functionName: 'totalAssets', }), publicClient.readContract({ address: VAULT_ADDRESS, abi: vaultPriceAbi, functionName: 'totalSupply', }), ]) // Derive unit price: totalAssets / totalSupply const unitPrice = totalSupply > 0n ? (totalAssets * 10n ** 18n) / totalSupply : 10n ** 18n // Default 1:1 if no supply console.log('Total assets:', totalAssets.toString()) console.log('Total supply:', totalSupply.toString()) console.log('Unit price (18 decimals):', unitPrice.toString()) // Convert between shares and assets at current rate const sharesFor1000 = await publicClient.readContract({ address: VAULT_ADDRESS, abi: vaultPriceAbi, functionName: 'convertToShares', args: [1000n * 10n ** 18n], }) console.log('Shares for 1000 assets:', sharesFor1000.toString()) ``` ```bash cast theme={null} # Read total assets and total supply cast call $VAULT_ADDRESS "totalAssets()(uint256)" --rpc-url $RPC_URL cast call $VAULT_ADDRESS "totalSupply()(uint256)" --rpc-url $RPC_URL # Convert between shares and assets cast call $VAULT_ADDRESS "convertToShares(uint256)(uint256)" 1000000000000000000000 --rpc-url $RPC_URL cast call $VAULT_ADDRESS "convertToAssets(uint256)(uint256)" 1000000000000000000000 --rpc-url $RPC_URL ``` ## Fee Reporting Multi-depositor vaults use the `PriceAndFeeCalculator` for fee computation instead of the `DelayedFeeCalculator` used by single-depositor vaults. The same two fee types apply -- management fees (percentage of AUM over time) and performance fees (percentage of gains above a high-water mark). See the [Single-Depositor Vaults](/guides/guardian/single-depositor-vaults#fee-reporting) guide for the conceptual explanation of how these fee types work. ### Reporting Fees Report fees by providing the current vault value to the fee vault. The `PriceAndFeeCalculator` uses its most recent snapshot to compute accrued fees. ```typescript viem theme={null} const feeVaultAbi = [ { name: 'reportFees', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'vaultValue', type: 'uint256' }], outputs: [], }, ] as const // Report current vault value for fee calculation const currentVaultValue = 10500000n * 10n ** 18n // Example: 10.5M in 18 decimals const { request: reportReq } = await publicClient.simulateContract({ address: FEE_VAULT_ADDRESS, abi: feeVaultAbi, functionName: 'reportFees', args: [currentVaultValue], account, }) const reportTx = await walletClient.writeContract(reportReq) await publicClient.waitForTransactionReceipt({ hash: reportTx, confirmations: 2 }) console.log('Fees reported for vault value:', currentVaultValue.toString()) ``` ```bash cast theme={null} # Report fees with current vault value cast send $FEE_VAULT_ADDRESS \ "reportFees(uint256)" \ 10500000000000000000000000 \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ### Claiming Fees The fee recipient claims accrued fees from the fee vault. Specify the token, amount, and recipient address. See the [FeeVault Contract Reference](/contract-reference/fee-vault) for full function details. ```typescript viem theme={null} const claimFeesAbi = [ { name: 'claimFees', type: 'function', stateMutability: 'nonpayable', inputs: [ { name: 'token', type: 'address' }, { name: 'amount', type: 'uint256' }, { name: 'recipient', type: 'address' }, ], outputs: [], }, { name: 'accruedFees', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }], }, ] as const // Check accrued fees first const accrued = await publicClient.readContract({ address: FEE_VAULT_ADDRESS, abi: claimFeesAbi, functionName: 'accruedFees', }) console.log('Accrued fees:', accrued.toString()) // Claim fees const FEE_TOKEN = '0x...' as const // Token to claim fees in const FEE_RECIPIENT = '0x...' as const // Address to receive fees const { request: claimReq } = await publicClient.simulateContract({ address: FEE_VAULT_ADDRESS, abi: claimFeesAbi, functionName: 'claimFees', args: [FEE_TOKEN, accrued, FEE_RECIPIENT], account, }) const claimTx = await walletClient.writeContract(claimReq) await publicClient.waitForTransactionReceipt({ hash: claimTx, confirmations: 2 }) console.log('Fees claimed:', claimTx) ``` ```bash cast theme={null} # Check accrued fees cast call $FEE_VAULT_ADDRESS "accruedFees()(uint256)" --rpc-url $RPC_URL # Claim fees cast send $FEE_VAULT_ADDRESS \ "claimFees(address,uint256,address)" \ $FEE_TOKEN \ $CLAIM_AMOUNT \ $FEE_RECIPIENT \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ## Transfer Hooks Multi-depositor vault units are ERC-20 tokens that can be transferred between addresses. Transfer hooks allow the vault owner to enforce restrictions on these transfers -- for example, compliance blocklists, lock-up periods, or allowlists for approved recipients. ### Setting Transfer Hooks Setting the transfer hook is an **owner operation** (not a guardian operation), but understanding the mechanism is important for guardians because transfer hooks affect how vault units flow between depositors. ```typescript viem theme={null} // Owner-only: set the transfer hook contract on the vault const setHookAbi = [ { name: 'setTransferHook', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'hook', type: 'address' }], outputs: [], }, ] as const const HOOK_ADDRESS = '0x...' as const // Contract implementing IBeforeTransferHook const { request: hookReq } = await publicClient.simulateContract({ address: VAULT_ADDRESS, abi: setHookAbi, functionName: 'setTransferHook', args: [HOOK_ADDRESS], account, // Must be vault owner }) const hookTx = await walletClient.writeContract(hookReq) await publicClient.waitForTransactionReceipt({ hash: hookTx, confirmations: 2 }) console.log('Transfer hook set:', hookTx) ``` ```bash cast theme={null} # Owner-only: set transfer hook on the vault cast send $VAULT_ADDRESS \ "setTransferHook(address)" \ $HOOK_ADDRESS \ --rpc-url $RPC_URL \ --private-key $OWNER_PRIVATE_KEY ``` When a transfer hook is configured, every vault unit transfer calls `beforeTransfer(address from, address to, uint256 amount)` on the hook contract before executing. If the hook reverts, the transfer fails with `MultiDepositorVault__TransferHookFailed`. See [Custom Hooks](/guides/guardian/custom-hooks) for building custom transfer hook contracts and the [Hook Interfaces Contract Reference](/contract-reference/hooks) for the `IBeforeTransferHook` interface. ## Emergency Procedures Emergency procedures for multi-depositor vaults follow the same pattern as single-depositor vaults. See the [Single-Depositor Vaults](/guides/guardian/single-depositor-vaults#emergency-procedures) guide for full details on when and why to use these. The key operations are summarized below. ### Pausing the Vault Calling `pause` immediately halts all guardian operations. Both the guardian and the vault owner can pause. ```typescript viem theme={null} 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) ``` ```bash cast theme={null} cast send $VAULT_ADDRESS "pause()" \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` 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. ```typescript viem theme={null} 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) ``` ```bash cast theme={null} cast call $VAULT_ADDRESS "paused()(bool)" --rpc-url $RPC_URL ``` ### Guardian Whitelist Check Verify that the guardian is still authorized on the vault's whitelist. ```typescript viem theme={null} 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) ``` ```bash cast theme={null} cast send $VAULT_ADDRESS "checkGuardianWhitelist()" \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ## Related Pages * [Single-Depositor Vaults](/guides/guardian/single-depositor-vaults) -- Base guardian operations for dedicated capital * [Custom Hooks](/guides/guardian/custom-hooks) -- Building and deploying custom hooks for vault operations * [Guardian Model](/guides/concepts/supported-protocols/aera-v3/guardian-model) -- How the guardian role works at the protocol level * [Cross-Chain](/guides/concepts/supported-protocols/aera-v3/cross-chain) -- Cross-chain vault architecture and Provisioner model * [Hooks](/guides/concepts/supported-protocols/aera-v3/hooks) -- How hooks validate and extend guardian operations * [MultiDepositorVault Contract Reference](/contract-reference/multi-depositor-vault) -- Full ABI for vault unit operations * [Provisioner Contract Reference](/contract-reference/provisioner) -- Deposit/redeem request and claim functions * [FeeVault Contract Reference](/contract-reference/fee-vault) -- Fee reporting and claiming functions * [Periphery Contract Reference](/contract-reference/periphery) -- PriceAndFeeCalculator and OracleRegistry # Single-Depositor Vaults Source: https://docs.gauntlet.xyz/guides/guardian/single-depositor-vaults Operating single-depositor vaults as a guardian -- setup, strategy execution, fee reporting, and emergency procedures 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](/guides/concepts/supported-protocols/aera-v3/guardian-model). Before starting, make sure you have: * A wallet with guardian permissions on the target vault * [viem](https://viem.sh) 2.x installed (`npm install viem`) or [Foundry](https://book.getfoundry.sh/) for the `cast` CLI * Familiarity with the [Aera V3 Guardian Model](/guides/concepts/supported-protocols/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. ```typescript viem theme={null} 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 ``` ```bash cast theme={null} # Set environment variables for all subsequent commands export RPC_URL_ETHEREUM="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY" export PRIVATE_KEY="0xYOUR_PRIVATE_KEY" # Vault and fee contract addresses — see Contract Reference for deployed addresses # https://docs.gauntlet.xyz/contract-reference/addresses export VAULT_ADDRESS="0x..." export FEE_CALCULATOR_ADDRESS="0x..." ``` ## 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](/contract-reference/base-vault). ```typescript viem theme={null} // 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) ``` ```bash cast theme={null} # For simple operations, use cast send directly. # Complex struct submissions (Operation[]) are typically scripted # rather than passed inline — use a Forge script or the viem approach above. cast send $VAULT_ADDRESS \ "submit((address,bytes,uint256,bytes32[],address,bytes,address,bytes)[])" \ "[($TARGET,$CALLDATA,0,[],0x0000000000000000000000000000000000000000,0x,0x0000000000000000000000000000000000000000,0x)]" \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` 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](/guides/developer/earn/displaying-data) for complete API coverage and endpoint details. **Via on-chain reads** -- Query the vault contract directly for current state: ```typescript viem theme={null} 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) ``` ```bash cast theme={null} # Read guardian address cast call $VAULT_ADDRESS "guardian()(address)" --rpc-url $RPC_URL # Check pause status cast call $VAULT_ADDRESS "paused()(bool)" --rpc-url $RPC_URL # Read vault owner cast call $VAULT_ADDRESS "owner()(address)" --rpc-url $RPC_URL ``` ### 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. ```typescript viem theme={null} // 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) ``` ```bash cast theme={null} # Multi-operation rebalance — typically scripted for complex batches. # For a simple two-operation rebalance: cast send $VAULT_ADDRESS \ "submit((address,bytes,uint256,bytes32[],address,bytes,address,bytes)[])" \ "[($PROTOCOL_A,$WITHDRAW_CALLDATA,0,[],0x0000000000000000000000000000000000000000,0x,0x0000000000000000000000000000000000000000,0x),($PROTOCOL_B,$DEPOSIT_CALLDATA,0,[],0x0000000000000000000000000000000000000000,0x,0x0000000000000000000000000000000000000000,0x)]" \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` 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](/guides/concepts/supported-protocols/aera-v3/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](/contract-reference/periphery) 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. ```typescript viem theme={null} 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()) ``` ```bash cast theme={null} # Report value to the fee calculator cast send $FEE_CALCULATOR_ADDRESS \ "reportValue(uint256)" \ 1000000000000000000000000 \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY # Report fees to the vault cast send $VAULT_ADDRESS \ "reportFees(uint256)" \ 1000000000000000000000000 \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ### Claiming Fees The fee recipient claims accrued fees from the vault. Specify the token, amount, and recipient address. ```typescript viem theme={null} 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) ``` ```bash cast theme={null} cast send $VAULT_ADDRESS \ "claimFees(address,uint256,address)" \ $FEE_TOKEN \ 50000000000000000000000 \ $FEE_RECIPIENT \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ### Monitoring Accrued Fees Check how much in fees has accrued and is available for claiming. ```typescript viem theme={null} 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()) ``` ```bash cast theme={null} cast call $VAULT_ADDRESS "accruedFees()(uint256)" --rpc-url $RPC_URL ``` ## 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. ```typescript viem theme={null} 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) ``` ```bash cast theme={null} cast send $VAULT_ADDRESS "pause()" \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` 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. ```typescript viem theme={null} 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) ``` ```bash cast theme={null} cast call $VAULT_ADDRESS "paused()(bool)" --rpc-url $RPC_URL ``` ### 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. ```typescript viem theme={null} 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) ``` ```bash cast theme={null} cast send $VAULT_ADDRESS "checkGuardianWhitelist()" \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY ``` ## Related Pages * [Multi-Depositor Vaults](/guides/guardian/multi-depositor-vaults) -- Guardian operations for shared liquidity pools with order solving * [Custom Hooks](/guides/guardian/custom-hooks) -- Building and deploying custom hooks for vault operations * [Guardian Model](/guides/concepts/supported-protocols/aera-v3/guardian-model) -- How the guardian role works at the protocol level * [Hooks](/guides/concepts/supported-protocols/aera-v3/hooks) -- How hooks validate and extend guardian operations * [BaseVault Contract Reference](/contract-reference/base-vault) -- Full ABI for submit, pause, and guardian functions * [FeeVault Contract Reference](/contract-reference/fee-vault) -- Fee reporting and claiming functions * [Periphery Contract Reference](/contract-reference/periphery) -- DelayedFeeCalculator and OracleRegistry # Go-Live Checklist Source: https://docs.gauntlet.xyz/integrate/go-live Pre-launch validation checklist for partner integrations with Gauntlet vaults Use this checklist before going live with your Gauntlet vault integration. Complete each section to verify that your integration is production-ready. ## Authentication * API key created and stored securely (not hardcoded in client-side code) * JWT token exchange tested successfully against production endpoint * Token refresh logic implemented to handle expiration gracefully * Rate limit handling in place with appropriate backoff strategy (see [Rate Limits](/onboarding/credentials)) * Error responses for authentication failures handled in your UI See [Credentials](/onboarding/credentials) for authentication setup details. ## Data Validation * Vault list endpoint returns expected vaults for your integration * Vault metrics (TVL, APY, share price) display correctly in your UI * User position data accurate after test deposit * Historical timeseries data rendering properly in charts and reports * Data refresh intervals appropriate for your use case (not over-polling) Test against mainnet with real data. Gauntlet does not maintain a separate testnet environment -- use small deposit amounts for validation. ## Integration Validation * Deposit flow tested with small amounts on mainnet * Withdrawal flow tested end-to-end (request through receipt of funds) * Error handling covers common failure modes: insufficient balance, slippage exceeds tolerance, gas estimation failures, and transaction reverts * Transaction confirmation and status tracking working * User-facing messaging accurate for all transaction states ## Fee Setup If your integration involves fee configuration (see [Fees](/integrate/fees)): * Fee recipient address configured on-chain * Fee accrual verified -- deposit, wait for fee period, check `accruedFees()` * Fee claiming tested via `claimFees()` function * Fee display accurate in your UI (if showing fees to end users) Fee setup applies primarily to Aera V3 vaults with a FeeVault contract. For other protocols, fee configuration is handled through your partnership agreement with Gauntlet. ## Attribution * Builder code received from Gauntlet and set as `builderCode` on `GauntletClient` (not self-serve — request from the Gauntlet partnerships team) * If using `step.payload` + `sendTransaction`: confirm `payload.data` ends with your ERC-8021 suffix (`0x8021` + UTF-8 hex of your builder code) * If using `step.tx` + `writeContract`: confirm `dataSuffix: step.tx.attribution` is passed — omitting it silently drops attribution even if the transaction succeeds * At least one test deposit confirmed attributed in the Gauntlet Developer Portal or via the events API ## Monitoring * Event tracking active for deposits, withdrawals, and transfers * Alerting configured for failed transactions and unexpected errors * Volume reporting pipeline operational (see [Tracking Attribution](/guides/developer/earn/tracking-volume)) * Logging sufficient to diagnose issues in production * Health check endpoint monitoring the Gauntlet API availability ## Security Review * API keys stored in secure secret management (not in source control) * JWT tokens not exposed to client-side code * Smart contract interaction addresses verified against official deployment * The SDK will not sign transactions * Input validation on all user-provided values (amounts, addresses) ## Final Steps Before going live: 1. **Complete all sections above** -- Address any unchecked items. 2. **Run a full end-to-end test** -- Simulate the complete user journey from authentication through deposit, data display, and withdrawal. 3. **Contact Gauntlet for production sign-off** -- Share your integration status with your partner representative. # Use Cases Source: https://docs.gauntlet.xyz/integrate/index Four use cases that cover a complete Gauntlet integration: discover vaults, deposit, show returns, and track attribution. ## Discover Top-Performing Vaults **Why:** Your users need to see which vaults are worth depositing into before they commit capital. Discovery is the entry point to every earn integration. **What:** Filter, compare, and rank Gauntlet vaults by APY, TVL, protocol, strategy, risk tier, and chain — all from a single API endpoint. **How:** Authenticate with the Gauntlet API, call `GET /v1/vaults` with your product's filters, sort results by the metric that matters most, and use vault detail endpoints to power your UI. Follow the full discovery flow with code examples for filtering, ranking, and rendering vault data. *** ## Deposit Your First Dollar **Why:** Getting a user into a vault is the core transaction in any earn product. The SDK makes this a single function call with partner-controlled signing. **What:** The SDK prepares deposit and withdrawal transactions — including approvals when needed — and returns normalized transaction objects. You sign and submit in your own wallet, MPC, or custody stack. **How:** Initialize the SDK with your RPC clients and `builderCode`, call `getDepositTx(...)`, then send the resulting steps through your signing infrastructure. Follow the end-to-end SDK deposit flow, see how withdrawals work, and confirm results through the API. *** ## Show User's Return **Why:** Users who can see their balance growing are more likely to stay deposited and deposit more. Return visibility is the most important retention surface. **What:** Current position value, ROI over time, per-vault breakdowns, and a recent transaction feed — all from the Gauntlet API. **How:** Call the user positions and timeseries endpoints with a wallet address to render portfolio charts, vault-level returns, and activity history. Follow the full return flow for positions, ROI charts, and recent activity. *** ## Track Your Attribution **Why:** You need to know how much volume your integration is driving for reporting, revenue reconciliation, and partner operations. **What:** Attribution is built into the SDK deposit flow automatically. The API surfaces the resulting activity data — transactions, events, and trend lines — for monitoring and reporting. **How:** Set your `builderCode` on `GauntletClient` (requested from Gauntlet — not self-serve) and attribution is embedded in every transaction automatically. Query the events and transactions endpoints to inspect attributed activity and trend it over time. See the full tracking flow for events, transactions, trend lines, and monitoring options. *** ## Go Live Once you've built all four use cases, validate the complete experience before launch. Validate discovery, onboarding, returns, and tracking before rolling out to production users. # Auth & Credentials Source: https://docs.gauntlet.xyz/onboarding/credentials Get your API key and start making authenticated requests. ## API Key After partner approval, Gauntlet provisions an API key for your organization. ```bash theme={null} GAUNTLET_API_KEY= ``` Store this in an environment variable — never in client-side code or version control. ## Make Authenticated Requests Pass the API key as a `Bearer` token in the `Authorization` header: ```bash theme={null} curl https://api.gauntlet.xyz/v1/vaults \ -H 'Authorization: Bearer ' ``` ```typescript theme={null} const API_BASE_URL = 'https://api.gauntlet.xyz' const API_KEY = process.env.GAUNTLET_API_KEY! const resp = await fetch(`${API_BASE_URL}/v1/vaults`, { headers: { Authorization: `Bearer ${API_KEY}` }, }) // returns: // { data: [{ name: "gtUSDa", ... }], meta: { ... } } ``` Or use the SDK, which handles auth internally: ```typescript theme={null} const sdk = new GauntletSDK({ apiKey: process.env.GAUNTLET_API_KEY, }) const vaults = await sdk.getVaults() // returns: // [{ name: "gtUSDa", ... }] ``` ## Rate Limits API access is rate-limited per partner: **60 requests/minute** and **10,000 requests/day**. | Header | Description | | ----------------------- | ---------------------------------------- | | `X-RateLimit-Limit` | Maximum requests allowed per window | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `X-RateLimit-Reset` | Unix timestamp when the window resets | On `429`, back off until `X-RateLimit-Reset`. Cache vault metrics and meta responses to reduce request volume. ## Error Codes | Status | Meaning | | ------ | --------------------------------------------- | | `401` | Missing or invalid API key | | `403` | Insufficient scope for this endpoint | | `429` | Rate limited — back off and retry after reset | ## Security Never expose your API key in client-side code, public repos, or browser-accessible bundles. Use server-side environments only. * Store keys in environment variables, add `.env` to `.gitignore` * Use separate keys for development, staging, and production * Rotate production keys every 90 days # Overview Source: https://docs.gauntlet.xyz/onboarding/index The Gauntlet Developer Kit is the easiest way to integrate with DeFi yield — add curated, institutional-grade yield to your app with a single SDK and API. # The Easiest Way to Integrate with DeFi Yield The Gauntlet Developer Kit gives you everything you need to add yield to your app — curated vaults, battle-tested infrastructure, and a complete integration layer so you don't have to build it yourself. Gauntlet integration architecture — Your App connects to the Gauntlet SDK, API, and Developer Portal, which sit on top of Gauntlet Vaults and infrastructure * **Gauntlet Vaults** — Institutional-grade yield strategies curated by Gauntlet across DeFi protocols and chains * **Vault Infrastructure** — Responsive, flexible, and secure onchain protocols and offchain systems that power those strategies * **Gauntlet SDK** — Single client for vault discovery, deposits, withdrawals, positions, and attribution. Wraps the API for data and communicates onchain via your RPC URLs for transactions. * **Gauntlet API** — The raw REST API underneath the SDK. Use it directly if you need full control or are working outside TypeScript. * **Gauntlet Developer Portal** — Register your app, provision API keys, track attribution, and manage payments ## Why Gauntlet Developer Kit? * **Easiest Access to DeFi Yield** — A single API to access, compare, and interact with yield opportunities across all Aera and Gauntlet vaults. * **Seamless Integration** — Integrate once and support all vaults and strategies. * **Chain-Agnostic** — Works across EVM and non-EVM ecosystems, including Ethereum, Base, Optimism, Arbitrum, and Solana. * **Monetizable** — Earn fees on deposits routed through your integration. Fee recipients are configured onchain via the FeeVault contract, with built-in attribution tracking for volume reporting and revenue reconciliation. * **Proven in Production** — Powers Aera and Gauntlet-managed vaults, backed by a team with 8+ years of DeFi experience and a long track record managing substantial TVL across DeFi protocols. ## Use Cases Use the API to filter, compare, and rank vaults before you ever ask a user to deposit. Use the SDK to prepare a deposit, sign in your own stack, and get a user into a Gauntlet vault. Use the API to show balances, ROI over time, and recent vault activity inside your product. Tracking is built into the SDK flow. Use the API to inspect and monitor the resulting activity data. ## Explore By Tab Four use cases that cover a complete Gauntlet integration. Install, initialize, and use the SDK for deposits and withdrawals. Vault discovery, user positions, returns, and events. Vault core, provisioners, fee accounting, adapters, and deployed addresses. # Quick Start Source: https://docs.gauntlet.xyz/onboarding/quickstart Deposit into a Gauntlet vault in minutes using the SDK. ```typescript theme={null} import { GauntletClient, getUserCurrentBalance } from '@gauntlet-xyz/sdk' import { getDepositTx, getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm' import { createPublicClient, createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { base } from 'viem/chains' const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const walletClient = createWalletClient({ account, chain: base, transport: http(process.env.RPC_URL_BASE!), }) const client = new GauntletClient({ evmClients: { [base.id]: createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!) }), }, wallet: walletClient, builderCode: 'your-builder-code', // request from Gauntlet — must be registered with the indexer for attribution to be counted }) const steps = await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, // 1 USDC (6 decimals) }) ``` ## Two Ways to Submit Each step in `steps` exposes two surfaces. Pick the one that fits your stack: ### `step.payload` — scripts and embedded wallets Attribution is pre-baked into `payload.data`. Because the calldata is fully formed, you can simulate the exact bytes that will hit the chain before spending gas, then wait for each receipt before moving to the next step. ```typescript theme={null} import { createPublicClient, http } from 'viem' import { base } from 'viem/chains' const publicClient = createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!) }) // steps may be [approve, requestDeposit] — execute in order for (const step of steps) { // Estimate gas before sending — simulates the exact calldata that will be broadcast. // Throws if the call would revert (e.g. insufficient balance, wrong allowance) so you // catch failures before spending gas. Returns the gas units needed for the tx. const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) // Send with the estimated gas limit — prevents out-of-gas failures on-chain. const { type: txType, account: _ignoredAccount, ...txPayload } = step.payload const hash = await walletClient.sendTransaction({ ...txPayload, account, gas }) // Wait for the receipt before proceeding to the next step. // Each step depends on the previous one being confirmed on-chain — the deposit will // revert if the approval hasn't landed yet. const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } ``` ### `step.tx` — browser wallets (wagmi) Use the structured ABI fields with `writeContract`. You must pass `step.tx.attribution` as `dataSuffix` — without it the transaction goes through but volume is not attributed. ```typescript theme={null} // wagmi — MetaMask, Coinbase Wallet, WalletConnect, etc. for (const step of steps) { await walletClient.writeContract({ address: step.tx.address, abi: step.tx.abi, functionName: step.tx.functionName, args: step.tx.args, account: step.tx.account, dataSuffix: step.tx.attribution, // required — omitting silently drops attribution }) } ``` ## Check Balance ```typescript theme={null} // balance will be in pendingDeposit until the vault solver settles it (~2 hours) const balances = await getUserCurrentBalance(client, { vaultId: VaultId.AeraUsdAlpha, address: '0xUser', }) console.log(balances) ``` ## Withdraw ```typescript theme={null} const withdrawSteps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, entireAmount: true, }) // same two paths apply — use whichever matches your stack for (const step of withdrawSteps) { const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const hash = await walletClient.sendTransaction({ ...step.payload, gas }) const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } ``` # Request Access Source: https://docs.gauntlet.xyz/onboarding/sign-up How to request Gauntlet API access for your organization — the approval process, what to expect, and next steps. Gauntlet provisions API access through a manual review process. There is no self-serve sign-up. Gauntlet reviews partnership requests individually. There is no self-serve sign-up at this time. Reach out to the Gauntlet partnerships team with: * Your organization name * Your intended use case (e.g., yield aggregation, portfolio display, custom vault deployment) * Technical contact name and email The Gauntlet partnerships team reviews your request. This is a manual process — expect a response within a few business days. You may be asked follow-up questions about your integration plans. Once approved, you receive an API key via secure channel. This is your credential for authenticating with the Gauntlet API and SDK. If you plan to use the SDK for deposits and withdrawals, also request a **builder code** at this stage. Your builder code is a short partner identifier that the Gauntlet indexer uses to attribute on-chain volume to your integration. It is separate from your API key — using an unregistered string will silently go untracked. **Next step:** Once you have your API key, head to [Auth & Credentials](/onboarding/credentials) to start making API calls. # Support & Help Source: https://docs.gauntlet.xyz/onboarding/support How to get help with your Gauntlet integration: contact channels, what to include in requests, escalation paths, and documentation resources. If you run into issues during integration or have questions about the platform, here is how to get help. ## Contact Reach out through your dedicated partner channel. If you don't have one yet, contact the Gauntlet partnerships team directly. The partnerships team typically responds within 1-2 business days. For urgent production issues, note the urgency in your message. ## What to include When reaching out for help, include the following so the team can assist you efficiently: * **Organization name** -- the name associated with your API access * **API key ID** -- the first 8 characters of your API key * **Error messages** -- full error response bodies, including `request_id` if present * **Steps to reproduce** -- what you were doing when the issue occurred * **Environment details** -- language, library versions, relevant code snippets ## Escalation For active integrations, dedicated partner channels may be available. Reach out to the Gauntlet partnerships team to discuss escalation options for your organization. If you have an existing dedicated channel (e.g., Slack or Telegram), use that for faster response times on integration-specific questions. ## Documentation resources If you have not already, check these sections of the docs before reaching out -- your answer may already be covered: * **[Integrate](/integrate/index)** -- step-by-step guides for deposits, withdrawals, and data display * **[API Reference](/onboarding/credentials)** -- endpoint details, request/response schemas, rate limits * **[Contracts](/contract-reference/overview)** -- on-chain contract addresses, ABIs, and vault architecture * **[Auth & Credentials](/onboarding/credentials)** -- token exchange, scopes, and security practices # Examples Source: https://docs.gauntlet.xyz/sdk/examples End-to-end SDK examples for vault discovery, live data, deposits, withdrawals, positions, activity, and error handling. ## Initialize ```typescript theme={null} import { GauntletClient } from '@gauntlet-xyz/sdk' import { createPublicClient, createWalletClient, http, privateKeyToAccount } from 'viem' import { base } from 'viem/chains' const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const publicClient = createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!), }) const walletClient = createWalletClient({ account, chain: base, transport: http(process.env.RPC_URL_BASE!), }) const client = new GauntletClient({ evmClients: { [base.id]: publicClient }, wallet: walletClient, builderCode: 'your-builder-code', // issued by Gauntlet — request from the partnerships team }) ``` ## Embedded Wallet (Privy) The SDK reads only `wallet.account.address` — it never signs. Any viem-compatible wallet works, including embedded wallets from Privy, Dynamic, or similar providers. For Privy, `@gauntlet-xyz/sdk/privy` sets up the whole client in one call: ```typescript theme={null} import { createGauntletClientFromPrivy } from '@gauntlet-xyz/sdk/privy' import { http } from 'viem' import { base } from 'viem/chains' import { useWallets } from '@privy-io/react-auth' const { wallets } = useWallets() const embeddedWallet = wallets.find(w => w.walletClientType === 'privy') const client = await createGauntletClientFromPrivy({ wallet: embeddedWallet, chains: [base], transports: { [base.id]: http(process.env.RPC_URL_BASE!) }, // optional — defaults to public RPC builderCode: 'your-builder-code', // issued by Gauntlet — request from the partnerships team }) ``` For other embedded wallet providers, build the viem clients yourself: ```typescript theme={null} import { GauntletClient } from '@gauntlet-xyz/sdk' import { createPublicClient, createWalletClient, custom, http } from 'viem' import { base } from 'viem/chains' const provider = await embeddedWallet.getEthereumProvider() const publicClient = createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!) }) const walletClient = createWalletClient({ account: embeddedWallet.address as `0x${string}`, chain: base, transport: custom(provider), }) const client = new GauntletClient({ evmClients: { [base.id]: publicClient }, wallet: walletClient, builderCode: 'your-builder-code', // issued by Gauntlet — request from the partnerships team }) ``` ## Deposit ```typescript theme={null} import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm' const steps = await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, // 1 USDC (6 decimals) receiver: '0xReceiver', // optional, defaults to wallet account }) // returns: // [ // { payload: { type: 'approve', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } }, // { payload: { type: 'requestDeposit', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } } // ] // steps must be executed in order — approve before requestDeposit for (const step of steps) { // Estimate gas — simulates exact calldata before broadcast, catches reverts before spending gas const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const hash = await walletClient.sendTransaction({ ...step.payload, gas }) // Wait for confirmation before the next step — deposit reverts if preceding approve is not mined const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } ``` ## Withdraw ```typescript theme={null} import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm' // Withdraw entire position const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, entireAmount: true, receiver: '0xReceiver', // optional; assets are sent here instead of the wallet account }) // returns: // [ // { payload: { type: 'requestRedeem', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } } // ] for (const step of steps) { const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const hash = await walletClient.sendTransaction({ ...step.payload, gas }) const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } ``` ```typescript theme={null} import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm' // By shares const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, shares: 500_000000000000000000n, }) // By asset amount const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 500_000n, }) ``` ## Check User Current Balance `getUserCurrentBalance` returns a unified view of all three balance states for a vault position. Call it on load and after any deposit or withdrawal transaction to keep your UI in sync. ```typescript theme={null} import { getUserCurrentBalance } from '@gauntlet-xyz/sdk' import { VaultId } from '@gauntlet-xyz/sdk/evm' const balances = await getUserCurrentBalance(client, { vaultId: VaultId.AeraUsdAlpha, address: '0xUser', }) console.log(balances) // [ // { // chain: 'base', // token: '0xUSDCADDRESS123', // decimals: 6 // pendingDeposit: 0n, // balance: 1_000_000n, // pendingWithdraw: 0n, // }, // ] ``` ### After an async deposit When a user deposits with `depositMode: 'async'`, the amount appears in `pendingDeposit` while the vault solver queues it. During this time the funds are locked in the provisioner contract and are not yet earning yield. They move to `balance` once the solver settles the request — usually within 2 hours. ```typescript theme={null} import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm' import { getUserCurrentBalance } from '@gauntlet-xyz/sdk' // Submit the async deposit const steps = await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 500_000n, depositMode: 'async', }) for (const step of steps) { const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const hash = await walletClient.sendTransaction({ ...step.payload, gas }) const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } // funds are locked in pendingDeposit — not yet earning — until solver settles (~2 hours) const balances = await getUserCurrentBalance(client, { vaultId: VaultId.AeraUsdAlpha, address: '0xUser', }) ``` ### After an async withdrawal When a user withdraws with `depositMode: 'async'`, the amount moves from `balance` to `pendingWithdraw`. During this time the vault shares have been redeemed and the assets are no longer earning yield, but they have not yet been transferred. Once the solver settles the request (usually within 2 hours), the assets become claimable as ERC-20 tokens in the receiver wallet. ```typescript theme={null} import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm' import { getUserCurrentBalance } from '@gauntlet-xyz/sdk' // Submit the async withdraw const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, entireAmount: true, depositMode: 'async', }) for (const step of steps) { const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const hash = await walletClient.sendTransaction({ ...step.payload, gas }) const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } // assets are in pendingWithdraw — no longer earning — until solver settles (~2 hours) const balances = await getUserCurrentBalance(client, { vaultId: VaultId.AeraUsdAlpha, address: '0xUser', }) ``` ### Sync deposit and withdrawal Sync transactions skip the queue. The balance moves immediately — no `pendingDeposit` or `pendingWithdraw`. Morpho withdrawals are sync. Aera mode support combines token settings with live runtime gates, so read it before presenting an instant action. When the V2 solving gate pauses a provisioner/token pair, both sync modes are unavailable. On Aera vaults, a sync deposit locks all of the depositor's vault units for 1 hour; redeeming or transferring them during that window reverts with `Aera__UnitsLocked`. ```typescript theme={null} import { getDepositTx, getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm' // Sync deposit with a Morpho vault: balance goes straight to `balance` const depositSteps = await getDepositTx(client, { vaultId: VaultId.BaseUsdcPrime, // Morpho vault — sync mode amount: 500_000n, }) for (const step of depositSteps) { const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const hash = await walletClient.sendTransaction({ ...step.payload, gas }) const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } // Sync withdraw: balance drops from `balance` immediately; // tokens appear in the receiver wallet right away const withdrawSteps = await getWithdrawTx(client, { vaultId: VaultId.BaseUsdcPrime, // Morpho vault — sync mode entireAmount: true, }) for (const step of withdrawSteps) { const gas = await publicClient.estimateGas({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const hash = await walletClient.sendTransaction({ ...step.payload, gas }) const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 }) if (receipt.status !== 'success') { throw new Error(`Transaction reverted: ${step.payload.type}`) } } ``` ### Quote and submit an Aera instant deposit Quote expected vault units, then pass the reviewed minimum to the explicit sync deposit. ```typescript theme={null} import { getDepositTx, getSyncDepositQuote, type PreparedTx, VaultId } from '@gauntlet-xyz/sdk/evm' const sendAndConfirm = async (step: PreparedTx) => { await publicClient.call({ account: step.payload.account, to: step.payload.to, data: step.payload.data, }) const hash = await walletClient.sendTransaction({ to: step.payload.to, data: step.payload.data, }) const receipt = await publicClient.waitForTransactionReceipt({ hash }) if (receipt.status !== 'success') throw new Error(`${step.tx.type} reverted`) } const request = { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, slippageBps: 100, } let quote = await getSyncDepositQuote(client, request) const build = () => getDepositTx(client, { ...request, depositMode: 'sync', minUnitsOut: quote.minUnitsOut, }) let steps = await build() if (steps[0]?.tx.type === 'approve') { await sendAndConfirm(steps[0]) quote = await getSyncDepositQuote(client, request) steps = await build() } await sendAndConfirm(steps[0]) ``` The sync approval spender is the vault. Requote after approval and review a lower `minUnitsOut` before requesting the deposit signature. ### Quote and submit an Aera instant withdrawal Use the same sizing input for the quote and transaction. Passing `syncWithdrawQuote` makes the transaction sync and pins the quoted on-chain bound. When transaction slippage is omitted, the builder uses the quote's slippage; an explicitly supplied value must match. ```typescript theme={null} import { getAeraTokenModeSupport, getSyncWithdrawQuote, getWithdrawTx, VaultId, } from '@gauntlet-xyz/sdk/evm' const vaultId = VaultId.AeraUsdAlpha const support = await getAeraTokenModeSupport(client, { vaultId }) if (support.syncRedeem) { const quote = await getSyncWithdrawQuote(client, { vaultId, amount: 500_000n, // exact USDC output account: account.address, // optional here; includes this account's lock state slippageBps: 50, }) if (quote.capacity.exceedsCapacity) { throw new Error('Requested amount exceeds the current sync-withdraw epoch capacity') } const steps = await getWithdrawTx(client, { vaultId, amount: 500_000n, syncWithdrawQuote: quote, }) for (const step of steps) { const hash = await walletClient.sendTransaction({ to: step.payload.to, data: step.payload.data, account: step.payload.account, }) const receipt = await publicClient.waitForTransactionReceipt({ hash }) if (receipt.status !== 'success') throw new Error('Sync withdrawal reverted') } } ``` For a full-position instant exit, the quote requires the account whose shares it reads. The transaction always uses the configured `wallet.account`; an optional `account` must match it. The builder rereads the current share balance and rejects a stale full-position quote. `shares` and `entireAmount` quotes with `slippageBps: 10000` are rejected because they would produce `minTokensOut: 0`. ```typescript theme={null} const quote = await getSyncWithdrawQuote(client, { vaultId: VaultId.AeraUsdAlpha, account: account.address, entireAmount: true, }) const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, account: account.address, entireAmount: true, syncWithdrawQuote: quote, }) ``` ## Read Live Vault Data `client.api` exposes every REST API endpoint with generated types — live metrics, timeseries, positions, activity, TVL, and prices. No RPC needed; a data-only client is just `new GauntletClient({ apiKey })`. ```typescript theme={null} import { apiVaultIdFromVaultId } from '@gauntlet-xyz/sdk' import { VaultId } from '@gauntlet-xyz/sdk/evm' // All vaults with live TVL / APY / unit price const { data: vaults } = await client.api.vaults() // One vault — the API identifies vaults by "{chainId}:{address}" const apiVaultId = await apiVaultIdFromVaultId(client, VaultId.AeraUsdAlpha) const { data: vault } = await client.api.vault(apiVaultId) // 30 days of daily TVL / unit-price / APY history const { data: points } = await client.api.vaultTimeseries(apiVaultId, { start: '2026-06-01', end: '2026-07-01', granularity: 'day', }) ``` User positions and PnL: ```typescript theme={null} // All positions for a wallet const { data: positions } = await client.api.positions('0xUser') // One position's value / cost-basis / PnL / ROI history const { data: history } = await client.api.positionTimeseries('0xUser', apiVaultId, { granularity: 'day', }) ``` Amounts are human-unit decimal strings (e.g. `"1250.5"`). Convert to base units exactly with `decimalToBigInt(value, token.decimals)` — it throws instead of rounding. ## Track Activity and Wait for Settlement `getActivityFlows` turns the wallet's raw activity log into one flow per user action, pairing async request/settle rows automatically: ```typescript theme={null} import { getActivityFlows } from '@gauntlet-xyz/sdk' const flows = await getActivityFlows(client.api, '0xUser') for (const flow of flows) { console.log(flow.kind, flow.status, flow.assets.decimal, flow.txHashes) } // deposit settled 1000 ['0xrequest...', '0xsettle...'] // withdraw pending 250.5 ['0xrequest...'] ``` After submitting an async deposit or withdrawal, block until the solver settles it: ```typescript theme={null} import { waitForRequestSettlement, SettlementTimeoutError } from '@gauntlet-xyz/sdk' try { const flow = await waitForRequestSettlement(client.api, '0xUser', requestHash) console.log(flow.status) // 'settled' or 'refunded' } catch (e) { if (e instanceof SettlementTimeoutError) { /* still pending after 10 minutes — poll again later */ } } ``` ## Position History Replay a wallet's full event history for one vault into a chronological timeline — share balance, escrowed pending amounts, and net asset flows after every event: ```typescript theme={null} import { getPositionHistory, apiVaultIdFromVaultId } from '@gauntlet-xyz/sdk' import { VaultId } from '@gauntlet-xyz/sdk/evm' const apiVaultId = await apiVaultIdFromVaultId(client, VaultId.AeraUsdAlpha) const { points } = await getPositionHistory(client.api, '0xUser', apiVaultId) const latest = points.at(-1) console.log(latest?.sharesBalance, latest?.netAssetsIn) ``` ## Wagmi / writeContract When integrating with wagmi, use `step.tx` fields with `writeContractAsync`. Pass `step.tx.attribution` as `dataSuffix` — wagmi appends it to the calldata before sending. **Omitting `dataSuffix` silently drops attribution: the transaction succeeds but volume is not tracked.** ```typescript theme={null} import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm' import { useWriteContract } from 'wagmi' const { writeContractAsync } = useWriteContract() const steps = await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, }) for (const step of steps) { await writeContractAsync({ address: step.tx.address, abi: step.tx.abi, functionName: step.tx.functionName, args: step.tx.args, account: step.tx.account, dataSuffix: step.tx.attribution, // required — omitting silently drops attribution }) } ``` ## Slippage Both `getDepositTx` and `getWithdrawTx` accept a `slippageBps` parameter (integer basis points, e.g. `50` = 0.5%). Defaults to `100` (1%). ```typescript theme={null} import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm' const steps = await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, slippageBps: 50, // 0.5% slippage tolerance }) ``` ## Error Handling ```typescript theme={null} import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm' import { VaultNotFoundError, AccountRequiredError, RpcNotConfiguredError, UnsupportedDepositModeError, InvalidSlippageBPSError, UnimplementedFeatureError, UnitConversionError, } from '@gauntlet-xyz/sdk' try { await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, }) } catch (e) { if (e instanceof VaultNotFoundError) { /* e.vaultId, e.chainId */ } if (e instanceof AccountRequiredError) { /* add wallet to GauntletClient config */ } if (e instanceof RpcNotConfiguredError) { /* e.chainId — add RPC URL for this chain */ } if (e instanceof UnsupportedDepositModeError) { /* e.vaultId, e.requested, e.available */ } if (e instanceof InvalidSlippageBPSError) { /* e.slippage — must be integer 0–10000 */ } if (e instanceof UnimplementedFeatureError) { /* e.feature */ } if (e instanceof UnitConversionError) { /* e.vaultAddress */ } } ``` Aera instant quote and transaction flows can throw `UnsupportedFeatureError` when the runtime is not sync capable. A zero or non-sync `minUnitsOut` throws `InvalidSyncDepositBoundError`. Instant withdrawals can also throw `StalePriceError`, `InvalidSyncWithdrawBoundError`, or `InvalidWithdrawParamsError` for invalid sizing or quote context. Full-position flows use `AccountRequiredError` when the quote omits `account`, and `AccountMismatchError` when the `account` passed with `getWithdrawTx({ entireAmount: true })` differs from the configured wallet. Data-path calls throw `GauntletApiError` on failed requests: ```typescript theme={null} import { GauntletApiError } from '@gauntlet-xyz/sdk' try { await client.api.position('0xUser', apiVaultId) } catch (e) { if (e instanceof GauntletApiError) { console.error(e.status, e.code, e.path) // e.g. 404 NOT_FOUND /v1/users/0xUser/positions/... } } ``` ## Go Deeper Full constructor, methods, result shapes, and errors. The full integration guide with confirmation and fallback guidance. # Installation Source: https://docs.gauntlet.xyz/sdk/installation Install the Gauntlet SDK and configure your project. ## Install ```bash pnpm theme={null} pnpm add @gauntlet-xyz/sdk viem ``` ```bash npm theme={null} npm install @gauntlet-xyz/sdk viem ``` ```bash yarn theme={null} yarn add @gauntlet-xyz/sdk viem ``` ```bash bun theme={null} bun add @gauntlet-xyz/sdk viem ``` The SDK uses [viem](https://viem.sh) as a peer dependency. Install a compatible `2.x` release in your application. ## Requirements * Node.js 18+ or any modern JavaScript runtime (Bun, Deno) * TypeScript 5+ recommended * An RPC URL for each chain you want to interact with (e.g. Alchemy, Infura, or your own node) * A Gauntlet API key from the [sign-up process](/onboarding/sign-up) * A Gauntlet **builder code** for attribution — request this alongside your API key from the Gauntlet partnerships team. Without it, deposits go through but volume is not attributed to your integration. RPC URLs, `PublicClient`, and `WalletClient` are only needed for the transaction path. If you only read data through `client.api`, an API key is all the configuration required. ## Peer Dependencies The SDK communicates directly on-chain using your RPC URLs. It reads allowances, share prices, and vault state, then returns transaction objects you sign and submit through your own wallet infrastructure. You need a viem `PublicClient` and `WalletClient` in your application. ```typescript theme={null} import { createPublicClient, createWalletClient, http } from 'viem' import { base } from 'viem/chains' const account = '0x0000000000000000000000000000000000000001' as const const publicClient = createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!), }) const walletClient = createWalletClient({ account, chain: base, transport: http(process.env.RPC_URL_BASE!), }) ``` All transaction and Aera quote APIs are available from either public entry point: ```typescript theme={null} import { getAeraTokenModeSupport, getSyncWithdrawQuote, getWithdrawTx, } from '@gauntlet-xyz/sdk' // The same exports are also available from '@gauntlet-xyz/sdk/evm'. ``` ## Next Steps Initialize the SDK and make your first deposit. Full constructor, methods, result shape, and errors. # SDK Overview Source: https://docs.gauntlet.xyz/sdk/overview Use the Gauntlet SDK for vault discovery, live vault data, deposits, withdrawals, user positions, activity, and attribution — all from one client. The Gauntlet SDK is the entry point for building integrations with Gauntlet. It handles vault discovery, live vault metrics, deposit and withdrawal transaction building, user balance and activity queries, and attribution — so you don't need to write low-level contract calls or raw API requests. Use it to: * discover and filter vaults by chain and protocol * read live vault metrics (TVL, APY, share price) and their history * prepare deposits and withdrawals with automatic approval handling * read live Aera sync/async token capabilities before showing an action * quote Aera instant withdrawals and pin the quote bounds into the transaction * query live user positions (pending, active, and queued withdrawals) and PnL * track a wallet's deposit/withdrawal lifecycle and wait for async settlement * carry attribution context on every transaction ## Three Paths, One Client **Vault discovery** — the SDK reads from a bundled vault manifest to give you typed access to all supported vaults, their deployments, accepted tokens, and deposit modes. No network request required. **Data path** — `client.api` is a typed client for the [Gauntlet REST API](/onboarding/credentials): live vault metrics and timeseries, user positions with PnL, the wallet activity log, aggregate TVL, and token prices. Response types are generated from the API's OpenAPI spec, so they cannot drift from the server. No RPC required. **Transaction path** — the SDK communicates on-chain via your RPC URLs to read allowances and vault state, then returns pre-encoded transaction objects you sign and submit. Uses your viem `PublicClient` and `WalletClient`. ```typescript theme={null} import { GauntletClient } from '@gauntlet-xyz/sdk' import { createWalletClient, http, createPublicClient } from 'viem' import { base } from 'viem/chains' const client = new GauntletClient({ evmClients: { [base.id]: createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!) }), }, wallet: createWalletClient({ account, chain: base, transport: http(process.env.RPC_URL_BASE!), }), }) ``` Signing stays entirely in your stack — the SDK never touches private keys. The `wallet` you provide is used to determine the sender account for allowance checks; you sign the resulting steps yourself. For Aera instant withdrawals, call `getAeraTokenModeSupport` first. Its sync flags include the live V2 solving gate and are false while that gate pauses the provisioner/token pair. When `syncRedeem` is true, call `getSyncWithdrawQuote` with exactly one of `amount`, `shares`, or `entireAmount: true`, then pass that quote as `syncWithdrawQuote` to `getWithdrawTx`. The builder validates that the quote still belongs to the same vault, chain, token, account, slippage, and request. If transaction slippage is omitted, the builder uses the quote's value; an explicit value must match. `entireAmount` quotes require an explicit `account` because quoting does not require a wallet. Transaction building always uses `wallet.account`; if you also pass `account`, it must match the wallet. `shares` and `entireAmount` quotes with `slippageBps: 10000` are rejected because they would produce `minTokensOut: 0`. Using an embedded wallet? `@gauntlet-xyz/sdk/privy` sets up a fully configured client from a Privy wallet in one call — see the [reference](/sdk/reference#privy). If you only need data — no transactions — construct the client with just an API key and use `client.api`: ```typescript theme={null} const client = new GauntletClient({ apiKey: process.env.GAUNTLET_API_KEY }) const { data: vaults } = await client.api.vaults() ``` ## Go Deeper Install the SDK and configure your project. Deposits, withdrawals, balance queries, and error handling. Constructor, methods, result shapes, and errors. The full integration guide with SDK and API confirmation. # SDK Reference Source: https://docs.gauntlet.xyz/sdk/reference Constructor, REST API client, data helpers, transaction methods, result shapes, and errors for the Gauntlet SDK. ## Constructor ```typescript theme={null} import { GauntletClient } from '@gauntlet-xyz/sdk' import { createPublicClient, createWalletClient, http } from 'viem' import { base } from 'viem/chains' const client = new GauntletClient({ evmClients: { [base.id]: createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!) }), }, wallet: createWalletClient({ account, chain: base, transport: http(process.env.RPC_URL_BASE!), }), }) ``` | Parameter | Type | Required | Description | | ----------------- | ---------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `evmClients` | `Record` | For transaction methods | Chain ID to viem PublicClient map | | `wallet` | `WalletClient` | For transaction methods | Any viem-compatible WalletClient — used only to read the sender address. The SDK never signs. | | `apiKey` | `string` | No | Partner API key from the Developer Portal — sent as `x-api-key` on `client.api` requests. Anonymous access is rate-limited. | | `apiUrl` | `string` | No | Override the REST API origin. Defaults to `https://api.gauntlet.xyz`. In a browser this may be a relative path (e.g. a Next.js rewrite like `/gauntlet-api`), which resolves against the page origin; outside a browser a relative value throws. | | `attributionMode` | `AttributionMode` | No | Defaults to `AttributionMode.PUBLIC` | | `builderCode` | `string` | No | Builder identifier for attribution — must be requested from Gauntlet, not self-serve. NOT the same as API key. Without it, transactions are unattributed. | ## Discover Vaults ```typescript theme={null} import { getVaults } from '@gauntlet-xyz/sdk/evm' // also available from the root: import { getVaults } from '@gauntlet-xyz/sdk' import { base } from 'viem/chains' const candidates = await getVaults(client, { chainId: base.id }) // returns: // [ // { // vaultId: "baseUsdcPrime", // name: "...", // protocol: "morpho", // deployments: [{ chainId: 8453, supplyToken: [{ symbol: "USDC", ... }], ... }] // }, // ... // ] ``` ## Fee Wrapper Vaults (Partners) Some partners integrate through a **fee wrapper vault**: an Aera vault that Gauntlet deploys exclusively for that partner, with the partner's fee terms applied. Because a fee wrapper vault belongs to one partner, it is not included in the SDK's bundled manifest. `getVaults` does not return it, and transaction functions throw `VaultNotFoundError` for its id. To make your fee wrapper vault accessible through the SDK, register it with `client.setManifest`. `setManifest` replaces the whole manifest, so read the bundled one from `client.manifest` and append your vault to keep the standard vaults available: ```typescript theme={null} const manifest = await client.manifest client.setManifest({ ...manifest, vaults: [ ...manifest.vaults, { vaultId: 'acmeUsdcFeeWrapper', // any unique id, you choose it name: 'Acme USDC', protocol: 'aera', strategy: 'Fee Wrapper', deployments: [ { chain: 'evm', chainId: 8453, vaultAddress: '0xYourFeeWrapperVault', // provided by Gauntlet vaultType: 'multi-depositor', supplyToken: [ { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', decimals: 6, }, ], }, ], }, ], }) ``` Gauntlet provides the deployment values (vault address, vault type, and supply tokens) when your fee wrapper vault is deployed. After registration, every SDK function accepts the id like any bundled vault: ```typescript theme={null} const steps = await getDepositTx(client, { vaultId: 'acmeUsdcFeeWrapper', amount: 1_000_000n, }) ``` The manifest lives on the client instance. Call `setManifest` once on each `GauntletClient` you construct, before the first call that references the vault. ## Transaction Functions Import from `@gauntlet-xyz/sdk/evm`. These communicate on-chain via your RPC URLs. Require `wallet` in the client config — the SDK reads the account address from `wallet.account`. ### getDepositTx The `vaultId` string resolves to a `VaultDeployment` from the manifest. This is how the SDK knows which token to approve (`supplyToken[0].address`), which contract to call (`vaultAddress` or `provisionerAddress`), and whether the vault supports sync or async deposits. ```typescript theme={null} import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm' // also available from the root: import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk' const steps = await getDepositTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, receiver: '0xReceiver', // optional, defaults to wallet account }) ``` | Parameter | Type | Required | Description | | ------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `vaultId` | `string` | Yes | Vault identifier — resolves token, contract, and deposit mode from the manifest | | `amount` | `bigint` | Yes | Amount in token base units | | `chainId` | `number` | No | Defaults to the vault's primary chain (Base for current multichain vaults) | | `assetSymbol` | `string` | No | Required for multi-asset vaults to select the asset token | | `depositMode` | `string` | No | Override deposit mode: `'async'` (queued) or `'sync'` (instant). When omitted, uses the vault's native mode — async for Aera, sync for Morpho. Vaults with `depositMode: 'both'` default to async. | | `receiver` | `Address` | No | Address that receives the minted vault units. Defaults to `wallet.account`. Aera V1 vaults require the receiver to equal the signer and throw `UnsupportedFeatureError` for any other address. On V2, sync deposits to a separate receiver require the receiver to approve the depositor first. | | `slippageBps` | `number` | No | Slippage tolerance in basis points (e.g. `100` = 1%). Defaults to `100`. Must be an integer between 0 and 10000. | | `minUnitsOut` | `bigint` | No | Caller-reviewed minimum output for an explicit Aera V2 sync deposit. Must be greater than zero. | ### getSyncDepositQuote Returns the expected vault units, slippage-adjusted minimum, numeraire value, and Instant Supply fee for an Aera V2 sync deposit. ```typescript theme={null} const quote = await getSyncDepositQuote(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, slippageBps: 100, }) // quote: { unitsOut, minUnitsOut, numeraireOut, feeBps, slippageBps } ``` `slippageBps` is required in the returned `SyncDepositQuote` and records the basis-point tolerance used to derive `minUnitsOut`. `feeBps` is the fee applied to the deposited token amount in basis points. `numeraireOut` is the post-fee deposit amount converted to the vault's numeraire — use this, not `unitsOut`, when displaying what the deposit is worth. Pass `quote.minUnitsOut` to `getDepositTx` with `depositMode: 'sync'`. The value must be positive. If approval is required, confirm it, request a fresh quote, and rebuild before asking for the deposit signature. ### getSyncDepositRate Reads the live Aera V2 Instant Supply fee without requiring a deposit amount. The exported `SyncDepositRateParams` type contains the same vault and token selectors used by `getSyncDepositQuote`. ```typescript theme={null} import { getSyncDepositRate, VaultId } from '@gauntlet-xyz/sdk/evm' const rate = await getSyncDepositRate(client, { vaultId: VaultId.AeraUsdAlpha, }) // rate: { feeBps } ``` | Parameter | Type | Required | Description | | ------------- | -------- | ---------------------- | -------------------------------------------------------------------------- | | `vaultId` | `string` | Yes | Aera V2 vault identifier | | `chainId` | `number` | No | Defaults to the vault's primary chain (Base for current multichain vaults) | | `assetSymbol` | `string` | For multi-asset vaults | Selects the deposit token | Unlike the withdraw rate, the deposit fee is a flat basis-point value with no price-age premium, so `feeBps` can be shown as soon as Instant Supply is selected, before an amount is entered. ### getAeraTokenModeSupport Reads currently available token modes from the live Aera runtime. Use the result to decide which deposit and withdrawal actions to show. ```typescript theme={null} import { getAeraTokenModeSupport, VaultId } from '@gauntlet-xyz/sdk/evm' const support = await getAeraTokenModeSupport(client, { vaultId: VaultId.AeraUsdAlpha, }) if (support.syncRedeem) { // Offer an instant withdrawal. } ``` Returns `Promise`: ```typescript theme={null} type AeraTokenModeSupport = { asyncDeposit: boolean asyncRedeem: boolean syncDeposit: boolean syncRedeem: boolean } ``` The values combine token configuration with live runtime requirements. Both sync flags are `false` while the V2 solving gate pauses the provisioner/token pair, and `syncRedeem` is also `false` unless the active fee calculator is V2. Read failures surface to the caller. | Parameter | Type | Required | Description | | ------------- | -------- | ---------------------- | -------------------------------------------------------------------------- | | `vaultId` | `string` | Yes | Aera vault identifier | | `chainId` | `number` | No | Defaults to the vault's primary chain (Base for current multichain vaults) | | `assetSymbol` | `string` | For multi-asset vaults | Selects the token | ### getSyncWithdrawRate Reads the live Aera V2 instant-withdraw multiplier without requiring a withdrawal size or account. The exported `SyncWithdrawRateParams` type contains the same vault and token selectors used by `getSyncWithdrawQuote`. ```typescript theme={null} import { getSyncWithdrawRate, VaultId } from '@gauntlet-xyz/sdk/evm' const rate = await getSyncWithdrawRate(client, { vaultId: VaultId.AeraUsdAlpha, }) // rate: { baseMultiplierBps, dynamicPremiumBps, effectiveMultiplierBps } ``` | Parameter | Type | Required | Description | | ------------- | -------- | ---------------------- | -------------------------------------------------------------------------- | | `vaultId` | `string` | Yes | Aera V2 vault identifier | | `chainId` | `number` | No | Defaults to the vault's primary chain (Base for current multichain vaults) | | `assetSymbol` | `string` | For multi-asset vaults | Selects the withdraw token | The three returned multipliers are bigint basis-point values. `effectiveMultiplierBps` is the base multiplier after subtracting the dynamic premium for the current oracle price age. ### getSyncWithdrawQuote Builds a block-consistent quote for an Aera V2 instant withdrawal without sending a transaction. ```typescript theme={null} import { getSyncWithdrawQuote, VaultId } from '@gauntlet-xyz/sdk/evm' const quote = await getSyncWithdrawQuote(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, slippageBps: 100, }) ``` Pass exactly one sizing mode: | Parameter | Type | Required | Description | | -------------- | --------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `vaultId` | `string` | Yes | Aera V2 vault identifier | | `amount` | `bigint` | One sizing mode | Exact token output; returns `kind: 'withdraw'` and `maxUnitsIn` | | `shares` | `bigint` | One sizing mode | Exact shares burned; returns `kind: 'redeem'` and `minTokensOut` | | `entireAmount` | `true` | One sizing mode | Quotes all shares owned by `account` | | `account` | `Address` | With `entireAmount` | Required for a full-position quote; optional for lock data on explicit amount/share quotes | | `chainId` | `number` | No | Defaults to the vault's primary chain (Base for current multichain vaults) | | `assetSymbol` | `string` | For multi-asset vaults | Selects the withdraw token | | `slippageBps` | `number` | No | Integer from 0–10000; defaults to 100 (1%). For `shares` and `entireAmount`, 10000 is rejected because it makes `minTokensOut` zero | The returned `SyncWithdrawQuote` includes estimated `shares` and `tokensOut`, executable `maxUnitsIn`/`minTokensOut` bounds, the effective rate, epoch capacity, optional `unitsLockedUntil`, and a block-stamped `context`. Redeem quotes also include `shareSafeTokensOut`, the largest exact-token withdrawal that leaves enough slippage headroom within the quoted shares. `capacity.knownLiquidityTokens` is the vault's current token balance when no pull-funds calldata is configured; it is undefined when the provisioner may source more liquidity. Capacity and lock fields are diagnostics; state can change after quoting, so the transaction may still revert on-chain. ### getWithdrawTx ```typescript theme={null} import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm' // also available from the root: import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk' // Withdraw all shares const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, entireAmount: true, receiver: '0xReceiver', // optional }) // Withdraw by shares const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, shares: 500_000000000000000000n, }) // Withdraw by asset amount const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, amount: 1_000_000n, }) ``` | Parameter | Type | Required | Description | | -------------------------------------- | ------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `vaultId` | `string` | Yes | Vault identifier | | `shares` \| `amount` \| `entireAmount` | — | Yes (one of) | Exact shares, exact asset amount, or full position | | `chainId` | `number` | No | Defaults to the vault's primary chain (Base for current multichain vaults) | | `assetSymbol` | `string` | No | Required for multi-asset vaults to select the withdraw token | | `depositMode` | `string` | No | Override withdraw mode: `'async'` (queued) or `'sync'` (instant). When omitted, uses the vault's native mode — async for Aera, sync for Morpho. Vaults with `depositMode: 'both'` default to async. | | `account` | `Address` | No | Only used with `entireAmount`. Defaults to `wallet.account`; when supplied, it must match the configured wallet. | | `receiver` | `Address` | No | Address that receives the withdrawn assets. Vault shares are always burned from the signer (`wallet.account`); `receiver` only redirects where the assets land. Defaults to `wallet.account`. Aera V1 vaults require the receiver to equal the signer and throw `UnsupportedFeatureError` for any other address. | | `slippageBps` | `number` | No | Slippage tolerance in basis points (e.g. `100` = 1%). Defaults to `100`. Must be an integer between 0 and 10000. | | `syncWithdrawQuote` | `SyncWithdrawQuoteBounds` | No | Pins a quote's `minTokensOut` or `maxUnitsIn` and implies sync mode. If `slippageBps` is omitted, the builder uses the quote's value; an explicit value must match. An explicit async request is rejected. | For a quoted sync withdrawal, pass the same sizing input. The builder reuses quote slippage when the transaction omits it: ```typescript theme={null} const quote = await getSyncWithdrawQuote(client, { vaultId: VaultId.AeraUsdAlpha, shares: 500_000000000000000000n, slippageBps: 50, }) const steps = await getWithdrawTx(client, { vaultId: VaultId.AeraUsdAlpha, shares: 500_000000000000000000n, syncWithdrawQuote: quote, }) ``` The builder validates the quote's vault, chain, token, account, slippage, and original sizing request. For `entireAmount`, it also rereads the wallet's current share balance and rejects stale quote shares. A quote created for another account cannot be used by the configured wallet. `shares` and `entireAmount` quotes with `slippageBps: 10000` are rejected because they would produce `minTokensOut: 0`. A sync deposit on an Aera vault locks all of the depositor's vault units for the vault's deposit refund timeout, currently 1 hour. Until the window ends, any withdrawal (sync or async) or transfer of the units reverts on-chain with `Aera__UnitsLocked`. Async deposits do not trigger the lock. ## User Vault Balance ### Balance lifecycle An async deposit or withdrawal passes through a **pending state** while the vault solver queues and processes the operation: * **`pendingDeposit`** — funds are locked in the provisioner contract. They are not yet earning yield. Once the solver settles the request, they move to `balance` and begin earning. * **`pendingWithdraw`** — vault shares have been redeemed but the underlying assets have not yet been transferred. They are no longer earning yield. Once the solver settles the request, they arrive as ERC-20 tokens in the receiver wallet. The solver typically processes requests within 2 hours; the maximum window is 12 hours. Funds are safe in both pending states — the delay is operational, not a risk. How an amount moves through the three states depends on whether the user chose sync or async: | Path | Where the balance lands | Notes | | -------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | Async deposit | `pendingDeposit` for \~2–12 hours (usually \~2), then moves to `balance` | Not earning during pending; best execution price once settled | | Sync deposit | Directly into `balance` | Slightly worse price; no waiting | | Sync withdraw | Removed from `balance` immediately; claimable as ERC-20 in the receiver wallet | Slightly worse price; no waiting | | Async withdraw | Moves from `balance` to `pendingWithdraw` for \~2–12 hours (usually \~2), then claimable as ERC-20 in the receiver wallet | No longer earning during pending; best execution price once settled | On Aera vaults, both withdraw paths revert with `Aera__UnitsLocked` while the user's units are locked: a sync deposit locks all of the user's vault units for 1 hour. ### getUserCurrentBalance ```typescript theme={null} import { getUserCurrentBalance, VaultId } from '@gauntlet-xyz/sdk' // VaultId also available from: import { VaultId } from '@gauntlet-xyz/sdk/evm' const balance = await getUserCurrentBalance(client, { vaultId: VaultId.AeraUsdAlpha, address: '0xUser', }) ``` | Parameter | Type | Required | Description | | --------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `vaultId` | `string` | Yes | Must resolve to an Aera multi-depositor vault — throws `UnsupportedProtocolError` otherwise | | `address` | `Address` | Yes | Account to query | | `chainId` | `number` | No | When omitted, returns one entry per chain the vault is deployed on. When provided, returns only that chain — throws `ChainMismatchError` if not deployed there. | Returns `Promise` — one entry per chain the vault is deployed on: ```typescript theme={null} type UserCurrentBalance = { chain: string // chain identifier, e.g. "base" — included for non-EVM compatibility token: Address // token address decimals: number // token decimals pendingDeposit: bigint // assets locked in provisioner after async deposit — not yet earning yield; 0n if none balance: bigint // assets actively earning in the vault; 0n if no position pendingWithdraw: bigint // assets redeemed but not yet claimable after async withdraw — no longer earning; 0n if none } ``` All numeric fields are always present. If the account has no position on a given chain, all three bigint fields are `0n` — this is not an error. ## Result Shape Both `getDepositTx` and `getWithdrawTx` return `Promise`. ```typescript theme={null} type PreparedTx = { payload: { type: string // 'approve' | 'deposit' | 'requestDeposit' | 'redeem' | 'requestRedeem' | 'withdraw' to: Address // contract to call data: Hex // ABI-encoded calldata with attribution suffix already concatenated account?: Address } tx: EvmTxStep // structured ABI fields + raw attribution bytes — use with writeContract } type EvmTxStep = { type: 'approve' | 'deposit' | 'requestDeposit' | 'redeem' | 'requestRedeem' | 'withdraw' address: Address // contract to call abi: Abi // ABI fragment for this call functionName: string args: readonly unknown[] account: Address // sender address attribution?: Hex // raw attribution bytes — must pass as dataSuffix to writeContract } ``` Steps must be executed in order. An `approve` step, when present, always comes first. Each step exposes two submission paths with different trade-offs: ### Path 1 — `step.payload` + `sendTransaction` **Use for:** backend scripts, embedded wallets (Privy, Dynamic), server-side signing, EVM pre-simulation (`eth_call` on the exact bytes to be broadcast). Attribution is pre-concatenated into `payload.data` — it cannot be lost regardless of wallet or provider. ```typescript theme={null} for (const step of steps) { await walletClient.sendTransaction(step.payload) } ``` ### Path 2 — `step.tx` + `writeContract` **Use for:** browser wallets via wagmi (MetaMask, Coinbase Wallet, WalletConnect), or when you need wagmi simulation hooks. Pass `step.tx.attribution` as `dataSuffix` — wagmi appends it to the ABI-encoded calldata before sending. **If `dataSuffix` is omitted or the EIP-1193 provider strips it, the transaction succeeds but volume is not attributed.** ```typescript theme={null} for (const step of steps) { await walletClient.writeContract({ address: step.tx.address, abi: step.tx.abi, functionName: step.tx.functionName, args: step.tx.args, account: step.tx.account, dataSuffix: step.tx.attribution, // required — omitting silently drops attribution }) } ``` How ERC-8021 builder codes work, why `dataSuffix` matters, and how to verify attribution is tracked. ## REST API Client — `client.api` `client.api` is a typed client for every endpoint of the [Gauntlet REST API](/onboarding/credentials). It is available on any configured `GauntletClient` (only `apiKey` is used — no RPC required), or standalone: ```typescript theme={null} import { GauntletApi } from '@gauntlet-xyz/sdk' const api = new GauntletApi({ apiKey: process.env.GAUNTLET_API_KEY }) const { data: vaults } = await api.vaults() ``` Response types are generated from the API's OpenAPI spec, so they match the server exactly. | Method | Endpoint | Returns | | ----------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------- | | `vaults(options?)` | `GET /v1/vaults` | All indexed vaults with live metrics (TVL, APY, unit price) | | `vaultsBySlug(slug)` | `GET /v1/vaults/slug/{slug}` | All enabled deployments for a logical vault | | `primaryVaultTimeseriesBySlug(slug, options?)` | `GET /v1/vaults/slug/{slug}/primary/timeseries` | Primary deployment TVL / unit-price / APY history, including resolved deployment provenance | | `vault(vaultId)` | `GET /v1/vaults/{id}` | One vault with live metrics | | `vaultDefinition(vaultId)` | `GET /v1/vaults/{id}/definition` | Raw indexed vault definition | | `vaultTimeseries(vaultId, options?)` | `GET /v1/vaults/{id}/timeseries` | TVL / unit-price / APY history | | `positions(wallet, options?)` | `GET /v1/users/{wallet}/positions` | All of a wallet's positions with PnL | | `position(wallet, vaultId)` | `GET /v1/users/{wallet}/positions/{id}` | One position with PnL breakdown | | `positionTimeseries(wallet, vaultId, options?)` | `GET /v1/users/{wallet}/positions/{id}/timeseries` | Value / cost-basis / PnL / ROI history | | `activity(wallet, options?)` | `GET /v1/users/{wallet}/activity` | One page of the wallet's immutable event log | | `activityRows(wallet, options?)` | — | Async iterator over the full activity log — follows pagination cursors for you | | `tvl(options?)` | `GET /v1/tvl` | Aggregate Gauntlet TVL, optionally with per-source breakdown | | `latestPrice(options)` | `GET /v1/prices` | Latest (or point-in-time) USD price for a token | | `priceTimeseries(options)` | `GET /v1/prices/timeseries` | USD price history for a token | | `health()` | `GET /health` | Service liveness (version + uptime) | | `chainSyncStatus()` | `GET /health/chains` | Per-chain indexer sync freshness | Timeseries and list methods accept `start` / `end` (ISO 8601), `granularity` (`'hour' | 'day' | 'week' | 'month'`), `limit`, `order`, and an opaque `next` cursor from the previous response's `meta.next_cursor`. Failed requests throw `GauntletApiError` with `.status`, `.path`, and a machine-readable `.code` when the API provides one. An aggregate response can also succeed while single items fail; those failures arrive in the response's `meta.partial_errors` as `PartialResponseError` entries (`code`, `message`, optional `resource_id`). The type is exported from the SDK. ### Units and vault ids The API emits amounts as **human-unit decimal strings** (e.g. `"1250.5"`) and identifies vaults by a **CAIP-10-style id** (`"{chainId}:{address}"`, lowercase address) rather than the manifest vault id. The SDK ships exact converters for both — they throw instead of silently rounding: | Helper | Description | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `decimalToBigInt(value, decimals)` | Decimal string → base-unit `bigint`. Throws `DecimalPrecisionError` if the value has more fractional digits than `decimals`. | | `bigIntToDecimal(value, decimals)` | Base-unit `bigint` → decimal string. | | `sharesToBigInt(value)` | Share amount → base-unit `bigint`. Vault shares are always 18 decimals (`SHARE_DECIMALS`). | | `apiVaultIdFromVaultId(client, vaultId, chainId?)` | Manifest vault id (e.g. `VaultId.AeraUsdAlpha`) → API CAIP-10 id. Defaults to the vault's primary chain (Base for current multichain vaults). | | `vaultIdFromApiVaultId(client, apiVaultId)` | API CAIP-10 id → manifest vault id, or `undefined` when the vault isn't in the bundled manifest (the API indexes more vaults than the manifest lists). | | `parseApiVaultId(id)` / `formatApiVaultId(chainId, address)` | Low-level CAIP-10 parsing/formatting. Parsing throws `InvalidCaipIdError` on malformed ids. | ## Activity Flows Raw activity rows are an immutable event log — an Aera async deposit, for example, is two rows (`deposit_pending`, then `deposit` or `deposit_refunded`) linked by `request_hash`. `getActivityFlows` fetches the log and stitches those lifecycles into one flow per user action, replacing client-side event-log scanning over RPC. ```typescript theme={null} import { getActivityFlows } from '@gauntlet-xyz/sdk' const flows = await getActivityFlows(client.api, '0xUser') const open = flows.filter(f => f.status === 'pending') ``` | Parameter | Type | Required | Description | | ----------------- | ------------- | -------- | ------------------------------------------------------- | | `api` | `GauntletApi` | Yes | Usually `client.api` | | `walletAddress` | `string` | Yes | Wallet to query | | `options.vaultId` | `string` | No | CAIP-10 vault id — narrows to one vault | | `options.maxRows` | `number` | No | Stop paginating after this many rows. Defaults to 1000. | Returns `Promise`, newest first: ```typescript theme={null} type ActivityFlow = { kind: 'deposit' | 'withdraw' | 'transfer_in' | 'transfer_out' status: 'settled' | 'pending' | 'refunded' vaultId: string // CAIP-10 id requestHash: string | null // Aera async correlation hash; null for sync flows requestedAt: Date | null // when the request row landed settledAt: Date | null // when the settle/refund row landed; null while pending assets: AssetAmount // magnitude of the asset movement (requested amount for refunds) shares: bigint // magnitude of the share movement, 18-decimal base units txHashes: string[] // request first, then settlement } type AssetAmount = { decimal: string // human-unit decimal string as the API emits it raw: bigint | null // base-unit integer; null when the token's decimals are unknown token: TokenRef | null } ``` The pure stitcher `stitchActivityFlows(rows)` is also exported if you fetch rows yourself. ### waitForRequestSettlement Polls the activity log until an Aera async request reaches a terminal state. Use after submitting a `requestDeposit` / `requestRedeem` transaction. ```typescript theme={null} import { waitForRequestSettlement } from '@gauntlet-xyz/sdk' const flow = await waitForRequestSettlement(client.api, '0xUser', requestHash) // flow.status is 'settled' or 'refunded' ``` | Option | Type | Description | | ---------------- | -------- | --------------------------------------------------------------------------- | | `vaultId` | `string` | CAIP-10 vault id — narrows polling to one vault | | `pollIntervalMs` | `number` | Defaults to 5000 | | `timeoutMs` | `number` | Defaults to 600000 (10 minutes). Throws `SettlementTimeoutError` on expiry. | ## Position History Replays a wallet's complete activity for one vault into a chronological position timeline — running share balance, escrowed pending amounts, and cumulative net asset flows after every event. Complements `client.api.positionTimeseries`, which gives sampled value/PnL history. ```typescript theme={null} import { getPositionHistory, apiVaultIdFromVaultId } from '@gauntlet-xyz/sdk' const apiVaultId = await apiVaultIdFromVaultId(client, VaultId.AeraUsdAlpha) const history = await getPositionHistory(client.api, '0xUser', apiVaultId) ``` Returns `Promise`. A wallet that has never touched the vault gets an empty timeline, not an error. ```typescript theme={null} type PositionHistory = { vaultId: string // CAIP-10 id token: TokenRef | null // the vault's asset token, when known points: PositionHistoryPoint[] // chronological, one per activity row } type PositionHistoryPoint = { timestamp: Date txHash: string type: string // activity row type, e.g. 'deposit', 'withdraw_pending' sharesDelta: bigint // signed share movement of this row, 18-decimal base units assetsDelta: string // signed asset movement, human decimal string sharesBalance: bigint // shares held after this row (escrowed redeem shares excluded) pendingDepositAssets: string // assets escrowed awaiting share mint pendingRedeemShares: bigint // shares escrowed awaiting asset return netAssetsIn: string // cumulative settled deposits minus settled withdrawals } ``` The pure builder `buildPositionHistory(rows)` is also exported. ## Privy `@gauntlet-xyz/sdk/privy` wires a Privy embedded or connected wallet into the SDK. Privy wallets are matched structurally (`{ address, getEthereumProvider() }`), so the SDK takes no `@privy-io` dependency. ```typescript theme={null} import { createGauntletClientFromPrivy } from '@gauntlet-xyz/sdk/privy' import { useWallets } from '@privy-io/react-auth' import { base } from 'viem/chains' const { wallets } = useWallets() const client = await createGauntletClientFromPrivy({ wallet: wallets[0], chains: [base], builderCode: 'your-builder-code', }) ``` | Parameter | Type | Required | Description | | ---------------------------------------------------- | ---------------------------- | -------- | --------------------------------------------------------------------------- | | `wallet` | `PrivyEthereumWallet` | Yes | The Privy wallet to sign with (e.g. `useWallets().wallets[0]`) | | `chains` | `[Chain, ...Chain[]]` | Yes | Chains the client should read from; the first is the wallet's signing chain | | `transports` | `Record` | No | Per-chain transport override; defaults to each chain's public RPC | | `apiKey`, `apiUrl`, `attributionMode`, `builderCode` | — | No | Passed through to `GauntletClient` | To wrap only the wallet (and build the rest of the client yourself), use `walletClientFromPrivy(wallet, chain)`, which returns a viem `WalletClient`. ## Types ### SyncWithdrawQuote and SyncWithdrawQuoteBounds `SyncWithdrawQuote` is a discriminated union. `kind: 'redeem'` carries the `shares` and `minTokensOut` required by on-chain `redeem`; `kind: 'withdraw'` carries the `tokensOut` and `maxUnitsIn` required by on-chain `withdraw`. ```typescript theme={null} type SyncWithdrawQuoteBounds = | { kind: 'redeem' shares: bigint minTokensOut: bigint context: SyncWithdrawQuoteContext } | { kind: 'withdraw' tokensOut: bigint maxUnitsIn: bigint context: SyncWithdrawQuoteContext } type SyncWithdrawQuote = SyncWithdrawQuoteBounds & { shares: bigint maxUnitsIn: bigint tokensOut: bigint minTokensOut: bigint shareSafeTokensOut?: bigint rate: SyncRedeemRate capacity: SyncWithdrawCapacity unitsLockedUntil?: bigint } type SyncWithdrawCapacity = { epochCapNumeraire: bigint epochRedeemedNumeraire: bigint remainingNumeraire: bigint remainingTokens: bigint knownLiquidityTokens?: bigint requestNumeraire: bigint exceedsCapacity: boolean } ``` The exported `SyncWithdrawQuote` is assignable to `SyncWithdrawQuoteBounds`, so the full quote can be passed directly to `getWithdrawTx`. ### VaultInfo ```typescript theme={null} type VaultInfo = { vaultId: string name: string protocol: 'aera' | 'morpho' strategy: string deployments: VaultDeployment[] } ``` ### VaultDeployment The object `vaultId` resolves to. Carries all metadata the SDK needs to construct deposit and withdraw transactions — you never supply these directly. ```typescript theme={null} type VaultDeployment = { chain: 'evm' chainId: number vaultAddress: Address // ERC4626 vault contract provisionerAddress?: Address // multi-depositor vaults: deposit routes through this instead vaultType: 'single-depositor' | 'multi-depositor' depositMode: 'sync' | 'async' | 'both' // validates the depositMode param in getDepositTx / getWithdrawTx supplyToken: TokenInfo[] // tokens accepted by this vault; provides address and decimals } ``` ### TokenInfo ```typescript theme={null} type TokenInfo = { symbol: string address: Address decimals: number } ``` ### VaultFilter ```typescript theme={null} type VaultFilter = { chainId?: number protocol?: string } ``` ### AttributionMode ```typescript theme={null} enum AttributionMode { PUBLIC = 'public', ENCODED = 'encoded', // not yet implemented PRIVATE = 'private', // not yet implemented } ``` ## Errors All errors extend `GauntletSDKError`, which extends `Error`. | Error | Description | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VaultNotFoundError` | Vault ID doesn't exist or isn't deployed on the requested chain. Has `.vaultId` and optional `.chainId` properties. | | `UnsupportedAssetError` | Token not accepted by this vault. Has `.asset` and `.vaultId` properties. | | `ChainMismatchError` | Chain parameter doesn't match vault deployment. Has `.expected` and `.received` properties. | | `UnsupportedDepositModeError` | Requested sync/async mode not supported by this vault. Has `.vaultId`, `.requested`, and `.available` properties. | | `RpcNotConfiguredError` | No `evmClients` entry provided for the required chain ID. Has `.chainId` property. | | `AccountRequiredError` | No wallet is configured for transaction building, or an `entireAmount` quote omits `account`. | | `UnsupportedProtocolError` | Vault protocol is not supported by this method (e.g. `getUserCurrentBalance` only supports Aera multi-depositor). Has `.protocol` property. | | `InvalidWithdrawParamsError` | The request does not provide exactly one sizing mode, or supplied quote bounds do not match the transaction request, including an account-scoped quote used by another wallet. | | `AccountMismatchError` | The `account` passed to `getWithdrawTx({ entireAmount: true })` does not match the configured wallet. Has `.expected` and `.received` properties. | | `InvalidSyncWithdrawBoundError` | A sync quote or transaction would submit a zero bound. Has `.bound`. | | `InvalidSyncDepositBoundError` | `minUnitsOut` is zero, negative, or supplied without explicit Aera sync mode. | | `StalePriceError` | The oracle price is too old for sync redeem. Has `.blockTimestamp`, `.maxPriceAge`, and `.priceTimestamp`. | | `InvalidSlippageBPSError` | `slippageBps` is not an integer in the range 0–10000. Has `.slippage` property. | | `UnimplementedFeatureError` | Feature exists in the API but is not yet implemented (e.g. `AttributionMode.ENCODED`). Has `.feature` property. | | `UnsupportedFeatureError` | The selected runtime cannot execute the feature, such as sync redeem without both V2 provisioner and fee calculator. Has `.feature` property. | | `UnitConversionError` | Failed to convert token units for a vault — fee calculator unavailable on-chain. Has `.vaultAddress` property. | | `GauntletApiError` | A `client.api` request failed. Has `.status`, `.path`, and optional `.code` (machine-readable API error code) properties. | | `InvalidDecimalError` | Value passed to a decimal converter is not a valid decimal string. Has `.value` property. | | `DecimalPrecisionError` | Converting a decimal string to base units would lose precision — the value has more fractional digits than the token's decimals. Has `.value` and `.decimals` properties. | | `InvalidCaipIdError` | Malformed CAIP-10 vault id (expected `"{chainId}:{address}"`). Has `.id` property. | | `SettlementTimeoutError` | `waitForRequestSettlement` deadline expired before the request settled. Has `.requestHash` and `.timeoutMs` properties. | ## Go Deeper End-to-end code for deposits, withdrawals, and error handling. Use the raw API directly if you need more control than the SDK provides.