Skip to main content
A vault is the fundamental unit of the Symmetry protocol. It is an on-chain account that holds a configurable set of SPL tokens with target weights, and mints its own SPL token representing proportional ownership.

Vault Account Structure

Every vault has these core properties:
FieldTypeDescription
versionu8Account version
ownAddressPublicKeyThe vault’s on-chain address (PDA derived from ["basket", vault_mint])
mintPublicKeyThe vault’s SPL token mint (PDA derived from ["mint", vault_id_u64])
supplyOutstandingu64Total supply of vault tokens currently in circulation
creation_timestampu64Unix timestamp of when the vault was created. Set once at creation, never modified.
settingsVaultSettingsAll vault configuration (fees, managers, schedule, automation, etc.)
accumulatedFeesVaultFeesAccumulated but unclaimed fees (symmetry, creator, host, managers)
lookupTablesLookupTablesAddress lookup tables for oracle accounts (2 active, 2 temp)
numTokensu8Number of active tokens in the vault
compositionAsset[100]Array of up to 100 token positions

Vault Token

Each vault mints its own SPL token:
  • Mint address is a PDA: ["mint", vault_id_u64] where vault_id is a sequential counter from the global config.
  • Vault account address is derived from the mint: ["basket", mint_pubkey].
  • Vault token holders have proportional ownership of all underlying tokens.
  • All vault tokens have 6 decimal places (MINT_DECIMALS = 6). This means 1,000,000 raw units = 1.0 vault tokens.
  • Vault token price = Total vault value / Total vault token supply.
  • The start_price parameter at creation sets the initial vault token price in human USD terms (e.g. "1.0" = $1.00). The SDK encodes this to the program’s internal fraction format. This determines how many vault tokens are minted per dollar of deposits — a higher start price means fewer tokens per deposit.

Composition

Each token position (Asset) in the vault contains:
FieldTypeDescription
mintPublicKeySPL token mint address
amountu64Current token amount held (in smallest units)
weightu16Target weight in basis points. All active weights must sum to 10,000 (100%).
activeu8Whether this token slot is active (1) or inactive (0)
oracleAggregatorOracleAggregatorOracle configuration for pricing this token
When prices are loaded via loadVaultPrice(), each asset also gets:
  • price — current oracle price
  • value — computed USD value of the position

Vault Types

TypeValueDescription
Private0Default. Vault settings (fees, schedule, automation, LP, metadata, managers, authorities) are configured individually via edit*Tx methods (e.g. editFeesTx, editManagersTx). The protocol also includes a direct private-basket settings instruction path for creator-controlled setup flows.
Public1Vault is publicly listed. Behavior otherwise identical to private; the distinction is used by frontends to filter discoverable vaults.
The vault type is set during creation and is exposed in vault.formatted.vault_type.

Formatted Vault

After fetching a vault, the formatted property provides a human-readable representation:
interface FormattedVault {
  pubkey: string;
  name: string;
  symbol: string;
  uri: string;
  version: number;
  own_address: string;
  mint: string;
  supply_outstanding: number;
  creator: string;
  host: string;
  vault_type: "private" | "public";
  bounty_mint: string;
  bounty_balance: number;
  start_price: number;
  high_watermark: number;
  active_rebalance: number;
  active_withdraws: number;
  active_managements: number;
  creation_timestamp: number;
  creator_settings: { creator: string };
  manager_settings: FormattedManagersSettings;
  fee_settings: FormattedFeeSettings;
  schedule_settings: FormattedScheduleSettings;
  automation_settings: FormattedAutomationSettings;
  lp_settings: FormattedLpSettings;
  metadata_settings: FormattedMetadataSettings;
  deposits_settings: { enabled: boolean };
  force_rebalance_settings: FormattedForceRebalanceSettings;
  custom_rebalance_settings: FormattedCustomRebalanceSettings;
  add_token_settings: { modification_delay: number; updated_at: number };
  update_weights_settings: { modification_delay: number; updated_at: number };
  make_direct_swap_settings: { modification_delay: number; updated_at: number };
  accumulated_fees: {
    symmetry_fees: number;
    creator_fees: number;
    host_fees: number;
    managers_fees: number;
  };
  last_automation_execution_timestamp: number;
  lookup_tables: FormattedLookupTables;
  composition: FormattedAsset[];
}

Metadata URI

The metadata_uri is a URL pointing to a JSON file that describes the vault and its token. This JSON populates the vault token’s on-chain metadata and is read by frontends to display vault information. The URI can be hosted anywhere (Arweave, IPFS, a static server, etc.) and must be at most 200 characters. The JSON file should contain the following fields:
{
  "name": "My Index Vault",
  "symbol": "MIV",
  "description": "A diversified index vault tracking top Solana ecosystem tokens.",
  "image": "https://arweave.net/your-token-image-url",
  "cover": "https://arweave.net/your-cover-image-url"
}
FieldRequiredDescription
nameYesThe vault token name. Should match the name passed to createVaultTx.
symbolYesThe vault token symbol/ticker. Should match the symbol passed to createVaultTx.
descriptionYesA human-readable description of the vault, displayed on frontends.
imageYesURL to the vault token’s image (icon/logo), used in wallets and token lists.
coverYesURL to the vault’s cover image, displayed on frontends.
You can include any additional fields for your own integrations — for example, social links, website URLs, or strategy details:
{
  "name": "My Index Vault",
  "symbol": "MIV",
  "description": "A diversified index vault tracking top Solana ecosystem tokens.",
  "image": "https://arweave.net/your-token-image-url",
  "cover": "https://arweave.net/your-cover-image-url",
  "website": "https://example.com",
  "twitter": "https://x.com/example",
  "discord": "https://discord.gg/example"
}
The metadata URI can be updated after creation using editMetadataTx.

Creating a Vault

const result = await sdk.createVaultTx({
  creator: wallet.publicKey.toBase58(),
  start_price: "1.0",
  name: "My Index Vault",
  symbol: "MIV",
  metadata_uri: "https://arweave.net/your-metadata-json",  // URL to JSON with name, symbol, description, image, cover
  host_platform_params: {
    host_pubkey: "<HOST_PUBKEY>",
    host_deposit_fee_bps: 10,
    host_withdraw_fee_bps: 10,
    host_management_fee_bps: 0,    // currently disabled in global config
    host_performance_fee_bps: 0,   // currently disabled in global config
  },
});

console.log("Vault mint:", result.mint);
console.log("Vault account:", result.vault);

await sdk.signAndSendTxPayloadBatchSequence({
  txPayloadBatchSequence: result,
  wallet,
});
  • start_price is denominated in USDC (6 decimals). A start price of "1.0" means 1 vault token = $1.00 initially.
  • Host platform fees are set at creation and cannot be changed later.
  • If host_platform_params is omitted, the creator becomes the host with zero host fees.
  • The vault automatically wraps WSOL for the required bounty bond + minimum automation bounty.
  • After creation, the vault has no tokens. You must add tokens with addOrEditTokenTx and set weights with updateWeightsTx.

Adding Tokens

After creating a vault, add tokens with their oracle configurations. For Pyth oracles, you can find price feed IDs for all supported assets at Pyth Price Feed IDs.
const tx = await sdk.addOrEditTokenTx(
  {
    vault: "<VAULT_PUBKEY>",
    manager: "<MANAGER_PUBKEY>",
  },
  {
    token_mint: "<TOKEN_MINT_ADDRESS>",
    active: true,
    min_oracles_thresh: 1,
    min_conf_bps: 10,
    conf_thresh_bps: 200,
    conf_multiplier: 1.0,
    oracles: [
      {
        oracle_type: "pyth",
        account_lut_id: 0,
        account_lut_index: 0,
        account: "<PYTH_PRICE_ACCOUNT>",
        weight_bps: 10000,
        is_required: true,
        conf_thresh_bps: 200,
        volatility_thresh_bps: 200,
        max_slippage_bps: 1000,
        min_liquidity: 0,
        staleness_thresh: 120,
        staleness_conf_rate_bps: 50,
        token_decimals: 9,
        twap_seconds_ago: 0,
        twap_secondary_seconds_ago: 0,
        quote_token: "usd",
      },
    ],
  }
);

await sdk.signAndSendTxPayloadBatchSequence({
  txPayloadBatchSequence: tx,
  wallet,
});

Setting Token Weights

Set target weights for all tokens. Weights are in basis points and must sum to 10,000:
const tx = await sdk.updateWeightsTx(
  {
    vault: "<VAULT_PUBKEY>",
    manager: "<MANAGER_PUBKEY>",
  },
  {
    token_weights: [
      { mint: "<TOKEN_MINT_1>", weight_bps: 5000 },
      { mint: "<TOKEN_MINT_2>", weight_bps: 3000 },
      { mint: "<TOKEN_MINT_3>", weight_bps: 2000 },
    ],
  }
);

Fetching Vaults

const vault = await sdk.fetchVault("<VAULT_PUBKEY>");

const vaultsMap = await sdk.fetchMultipleVaults(["<PUBKEY_1>", "<PUBKEY_2>"]);

const allVaults = await sdk.fetchAllVaults();

const creatorVaults = await sdk.fetchCreatedVaults("<CREATOR_PUBKEY>");
const hostedVaults = await sdk.fetchHostedVaults("<HOST_PUBKEY>");
const managedVaults = await sdk.fetchManagedVaults("<MANAGER_PUBKEY>");

const mintMap = await sdk.fetchVaultsFromMints(["<VAULT_MINT_1>", "<VAULT_MINT_2>"]);

// Derive vault address from mint without an RPC call
const addrMap = await sdk.deriveVaultsByMints(["<VAULT_MINT>"]);

Loading Prices

Calling fetchVault returns the vault without live prices. To get current prices and TVL:
let vault = await sdk.fetchVault("<VAULT_PUBKEY>");
vault = await sdk.loadVaultPrice(vault);

console.log(vault.tvl);    // Decimal — total value locked
console.log(vault.price);  // Decimal — price per vault token

for (const asset of vault.formatted!.composition) {
  console.log(asset.mint, asset.amount, asset.weight);
}

Vault Settings

Each vault has independently configurable settings, each with its own modification delay and authority bitmask:
SettingKey FieldsDelay Field
Creatorcreator pubkeyNo delay (immediate)
Managersmanagers[] (pubkey, fee_split_weight, authorities)modification_delay
Feesdeposit/withdraw fees for creator+managers, vault deposit/withdraw fees (management/performance fees currently disabled in global config)modification_delay
Schedulecycle timing, deposit/automation/management windowsmodification_delay
Automationenabled, slippage/activation thresholds, cooldownmodification_delay
LPenabled, lp_threshold_bpsmodification_delay
Metadataname, symbol, URImodification_delay
DepositsenabledNo delay (immediate)
Force Rebalanceenabledmodification_delay
Custom Rebalanceenabledmodification_delay
Add Token Delaymodification delay for adding tokensmodification_delay
Update Weights Delaymodification delay for weight changesmodification_delay
Direct Swap Delaymodification delay for direct swapsmodification_delay

Deposits Setting

Controls whether users can deposit into the vault. When enabled: false, no new deposits are accepted. This change is always immediate (no modification delay). Configured via editDepositsTx.

Force Rebalance Setting

Controls whether authorized managers can trigger rebalances that bypass the normal automation checks. When a manager with the force_rebalance authority bit triggers a rebalance via rebalanceVaultTx:
  • Automation does not need to be enabled.
  • The schedule automation window is not checked.
  • The cooldown period is not enforced.
  • The deviation threshold is not required.
When enabled: false, this override is disabled and all rebalances must go through normal automation conditions. This is a powerful privilege — use the authority bitmask to restrict which managers can force rebalances. Configured via editForceRebalanceTx.

Custom Rebalance Setting

Controls whether custom rebalances (type VaultCustom) are allowed. This is a separate rebalance mode from the standard keeper-initiated rebalance. Configured via editCustomRebalanceTx.

LP Setting

Controls whether the vault operates in LP (liquidity provider) mode. When enabled: true, lp_threshold_bps sets the maximum deviation (in bps) from target weights before LP swaps are restricted. Configured via editLpTx.

High Watermark

The high_watermark tracks the vault token’s all-time high price. It is used to compute performance fees — fees are only charged on profits above the high watermark, preventing double-charging on recovery from drawdowns. When the vault token price exceeds the current high watermark, the difference is the “profit” subject to the performance fee. The high watermark is updated on-chain when fees are processed. Accessible via vault.formatted.high_watermark.

Active Counters

The vault tracks how many operations are currently in progress:
CounterFieldDescription
Active Rebalanceactive_rebalanceNumber of vault-level rebalances currently in progress. When > 0, new keeper-initiated rebalances are blocked (isRebalanceRequired() returns false).
Active Withdrawalsactive_withdrawsNumber of withdrawals currently being processed.
Active Managementsactive_managementsNumber of management configuration intents currently pending.
These counters are incremented when operations are created on-chain and decremented when they complete. Accessible via vault.formatted.

Schedule & Cycles

Each vault has a configurable schedule that divides time into repeating cycles. Within each cycle, three operation windows control when specific activities are allowed:
WindowFieldsWhat it gates
Depositsdeposits_start, deposits_endWhen users can deposit into the vault
Automationautomation_start, automation_endWhen automated operations can run
Managementmanagement_start, management_endWhen management actions can be taken

How Cycles Work

  1. cycle_start_time is a unix timestamp marking the first cycle’s start.
  2. cycle_duration is the length of each cycle in seconds (e.g., 86400 for daily, 604800 for weekly).
  3. The current position within the cycle is: (current_time - cycle_start_time) % cycle_duration.
  4. Each window’s start and end are offsets (in seconds) from the beginning of the cycle.
  5. An operation is allowed when the current cycle position falls within its window.
If cycle_duration is 0, there is no cycle constraint and all operations are always allowed.
Each schedule window must be at least 10 minutes wide (600 seconds). The cycle_duration must also be at least 10 minutes unless it’s set to 0 (which disables all schedule restrictions). Setting shorter windows will cause a validation error.

Example: Daily Cycle with Restricted Windows

const tx = await sdk.editScheduleTx(
  { vault: "<VAULT_PUBKEY>", manager: wallet.publicKey.toBase58() },
  {
    cycle_start_time: 1700000000,           // when cycles begin (unix timestamp)
    cycle_duration: 86400,                  // 24-hour cycle (seconds)
    deposits_start: 0,                      // deposits open at cycle start
    deposits_end: 86400,                    // deposits open all day
    automation_start: 3600,                 // automation starts 1 hour into cycle
    automation_end: 82800,                  // automation ends 1 hour before cycle end
    management_start: 0,                    // management open at cycle start
    management_end: 43200,                  // management closes halfway through
    modification_delay: 86400,              // 24h delay for future schedule changes
  }
);

Example: Always Open (Default)

Setting all windows to cover the full cycle duration means no restrictions:
{
  cycle_start_time: 0,
  cycle_duration: 86400,
  deposits_start: 0,
  deposits_end: 86400,
  automation_start: 0,
  automation_end: 86400,
  management_start: 0,
  management_end: 86400,
  modification_delay: 0,
}

Use Cases

  • Daily rebalance window: Set automation_start and automation_end to a narrow window (e.g., 2 hours) so automated rebalances only run during low-activity periods.
  • Weekly management cycle: Use a 7-day cycle with management limited to the first day, giving vault holders predictability about when settings may change.
  • Deposit lockout during rebalancing: Restrict deposits_end to close deposits before the automation window opens, preventing new deposits while the vault is rebalancing.
The schedule interacts with the automation system: isRebalanceRequired() checks whether the current time falls within the automation window before proceeding.

Lookup Tables

Each vault uses 2 active Address Lookup Tables (ALTs) to store oracle account addresses needed for price updates. When tokens are added and the ALTs become full, use rewriteLookupTablesTx to rebuild them.

Constants

ConstantValue
Max tokens per vault100
Max managers per vault10
Max oracles per token4
Max accounts per oracle4
Weight unitBasis points (bps), 10,000 = 100%
Vault token decimals6
Symbol max length10 characters
Name max length32 characters
URI max length200 characters