Fees
Symmetry has a multi-tier fee system. Fees are measured in basis points (bps), where 10,000 bps = 100%.
Fee Categories
| Category | Description |
|---|
| Deposit Fee | Charged when users deposit tokens into the vault. Computed as a percentage of the vault tokens being minted, and deducted before the remainder is sent to the depositor. |
| Withdrawal Fee | Charged when users withdraw tokens from the vault. Computed as a percentage of the vault tokens being burned, and deducted before the proportional underlying tokens are released. |
| Management Fee | Ongoing fee charged over time, specified as an annualized rate. For example, 100 bps = 1% per year. The fee is accrued continuously and deducted from the vault’s value proportionally over time. |
| Performance Fee | Charged on profits above the vault’s high watermark — the all-time high vault token price. Only the gain above the high watermark is subject to this fee. For example, if the high watermark is 1.00andthevaulttokenpricerisesto1.10, the performance fee applies to the 0.10gain.Ifthepricelaterdropsto0.90 and recovers to 1.05,noperformancefeeischargedbecausethepricehasn′texceededtheprevious1.10 high watermark. This prevents double-charging on recovery from drawdowns. |
Management fees and performance fees are currently disabled at the protocol level (global config). Setting non-zero values for these fees in vault configuration will have no effect until they are re-enabled. Only deposit and withdrawal fees are active. See Global Config for current status.
When enabled, performance fees are collected via supply dilution: the protocol mints new vault tokens to fee recipients (host, creator, managers, protocol) rather than removing underlying tokens from the vault. This increases the total vault token supply, which proportionally dilutes existing holders by the fee amount. The high watermark is updated after minting to prevent double-charging.
Fee Tiers
Each fee category is split across 4 vault-level configurable tiers plus a protocol fee layer (global config), collected independently:
| Tier | Set By | Modifiable |
|---|
| Host | Set at vault creation | Never (immutable) |
| Creator | Creator or authorized manager | Via editFeesTx (subject to modification delay) |
| Managers | Authorized manager | Via editFeesTx (subject to modification delay) |
| Vault | Authorized manager (deposit & withdraw only) | Via editFeesTx (subject to modification delay) |
| Symmetry Protocol | Protocol admin | Via global config |
Host Fees (Immutable)
Set at vault creation and cannot be changed:
host_platform_params: {
host_pubkey: "<HOST_PUBKEY>",
host_deposit_fee_bps: 10,
host_withdraw_fee_bps: 10,
host_management_fee_bps: 0,
host_performance_fee_bps: 0,
}
Creator/Manager Fees
const tx = await sdk.editFeesTx(
{ vault: "<VAULT>", manager: "<MANAGER>" },
{
creator_deposit_fee_bps: 50,
creator_withdraw_fee_bps: 50,
creator_management_fee_bps: 100, // currently disabled in global config
creator_performance_fee_bps: 500, // currently disabled in global config
managers_deposit_fee_bps: 0,
managers_withdraw_fee_bps: 0,
managers_management_fee_bps: 0, // currently disabled in global config
managers_performance_fee_bps: 0, // currently disabled in global config
vault_deposit_fee_bps: 0,
vault_withdraw_fee_bps: 0,
modification_delay: 86400,
}
);
Vault Fees
vault_deposit_fee_bps and vault_withdraw_fee_bps stay inside the vault rather than being distributed. This fee benefits all existing vault token holders.
Protocol Fees
Set in the global config by the protocol admin:
| Setting | Description |
|---|
symmetry_deposit_fee_bps | Flat deposit fee |
symmetry_deposit_fee_share_bps | Share of total deposit fees going to protocol |
symmetry_withdraw_fee_bps | Flat withdrawal fee |
symmetry_withdraw_fee_share_bps | Share of total withdrawal fees going to protocol |
symmetry_management_fee_bps | Flat management fee (currently 0) |
symmetry_management_fee_share_bps | Share of total management fees going to protocol (currently 0) |
symmetry_performance_fee_bps | Flat performance fee (currently 0) |
symmetry_performance_fee_share_bps | Share of total performance fees going to protocol (currently 0) |
symmetry_trade_fee_bps | Trade fee |
symmetry_limit_order_fee_bps | Limit order fee |
Fee Limits
The global config enforces maximum fee limits: max_deposit_fee_bps, max_withdraw_fee_bps, max_management_fee_bps, and max_performance_fee_bps.
Fee Accumulation & Claiming
Fees accumulate in the vault’s accumulatedFees field, tracked separately for each tier:
interface FormattedAccumulatedFees {
symmetry_fees: number;
creator_fees: number;
host_fees: number;
managers_fees: number;
}
Claiming fees is a two-step process:
Step 1: Withdraw fees from vault
const tx = await sdk.withdrawVaultFeesTx({
claimer: wallet.publicKey.toBase58(),
vault: "<VAULT_PUBKEY>",
});
This automatically determines which fee types the claimer can collect based on their role, creates WithdrawVaultFees accounts, and transfers the fee tokens.
Step 2: Claim remaining tokens (if needed)
const tx = await sdk.claimTokenFeesFromVaultTx({
claimer: wallet.publicKey.toBase58(),
withdrawVaultFees: "<WITHDRAW_VAULT_FEES_PUBKEY>",
});
Manager Fee Splitting
Manager fees are split based on fee_split_weight_bps. All manager weights must sum to 10,000. Example: if manager A has weight 6,000 and manager B has weight 4,000, A gets 60% and B gets 40%.
WithdrawVaultFees Account
There are 4 WithdrawVaultFees accounts per vault (one per fee type: symmetry=0, creator=1, host=2, managers=3). Each stores:
- The vault it belongs to
- Owners and their weight splits
- Accumulated fee tokens and amounts
Fetching Fee Accounts
const fees = await sdk.fetchAllWithdrawVaultFees({ type: "vault", pubkey: "<VAULT>" });
const managerFees = await sdk.fetchManagerWithdrawVaultFees("<MANAGER_PUBKEY>");
const creatorFees = await sdk.fetchCreatorWithdrawVaultFees("<CREATOR_PUBKEY>");
const hostFees = await sdk.fetchHostWithdrawVaultFees("<HOST_PUBKEY>");
const symmetryFees = await sdk.fetchSymmetryWithdrawVaultFees("<SYMMETRY_PUBKEY>");
Oracles
Each token in a vault has an oracle aggregator that computes its price from up to 4 oracle sources.
Oracle Types
| Type | Enum | String | Description |
|---|
| Pyth | 0 | pyth | Pyth Network price feeds via Hermes (Price Feed IDs) |
| Raydium CLMM | 1 | raydium_clmm | Raydium Concentrated Liquidity AMM TWAP (currently disabled on-chain) |
| Raydium CPMM | 2 | raydium_cpmm | Raydium Constant Product AMM TWAP |
| Switchboard | 3 | switchboard | Switchboard oracle feeds (currently disabled on-chain) |
| Example | 255 | example | Placeholder |
Raydium CLMM and Switchboard oracles are currently disabled on-chain. Only Pyth and Raydium CPMM oracle types can be used. Attempting to configure a token with a disabled oracle type will fail.
Quote Tokens
Oracle prices can be denominated in:
| Quote | Enum | String |
|---|
| USDC | 0 | usdc |
| WSOL | 1 | wsol |
| USD | 2 | usd |
The quote_token field tells the protocol what denomination the oracle feed reports prices in. For Pyth, most price feeds are denominated in USD, so use "usd". If a feed reports prices in USDC or WSOL, use the corresponding quote token — the protocol will automatically convert to a common base using the on-chain WSOL/USD and USDC/USD Pyth feeds.
Oracle Configuration
Each oracle source has these settings:
interface OracleInput {
oracle_type: "pyth" | "raydium_clmm" | "raydium_cpmm" | "switchboard" | "example";
account_lut_id: number;
account_lut_index: number;
account: string;
weight_bps: number;
is_required: boolean;
conf_thresh_bps: number;
volatility_thresh_bps: number;
max_slippage_bps: number;
min_liquidity: number;
staleness_thresh: number;
staleness_conf_rate_bps: number;
token_decimals: number;
twap_seconds_ago: number;
twap_secondary_seconds_ago: number;
quote_token: "usdc" | "wsol" | "usd";
}
account_lut_id (0 or 1) selects which of the vault’s two Address Lookup Tables contains the oracle account. account_lut_index is the position within that LUT. When adding a new token, use account_lut_id: 0 and account_lut_index: 0 — the SDK and rewriteLookupTablesTx will handle LUT management. After adding multiple tokens, call rewriteLookupTablesTx to rebuild the LUTs with all oracle accounts.
Oracle Aggregator
The aggregator combines prices from multiple oracle sources using weighted percentile calculations:
interface FormattedOracleAggregator {
num_oracles: number;
min_oracles_thresh: number;
oracles: FormattedOracle[];
min_conf_bps: number;
conf_thresh_bps: number;
conf_multiplier: number;
}
The weight_bps values across all oracles for a single token must sum to exactly 10,000 (100%). Additionally, min_conf_bps must be strictly less than conf_thresh_bps.
Default Oracle Settings (Pyth)
{
oracle_type: "pyth",
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,
twap_seconds_ago: 0,
twap_secondary_seconds_ago: 0,
quote_token: "usd",
}
Pyth Integration
For Pyth oracles, the account field is the Pyth price account pubkey. You can look up price feed IDs for all supported assets at Pyth Price Feed IDs. During price updates, the SDK:
- Fetches feed IDs from the on-chain price accounts.
- Requests VAAs from the Pyth Hermes API.
- Creates, initializes, writes, and verifies VAAs on-chain.
- Updates the individual price feeds.
- Runs the vault’s
updateTokenPrices instruction.
- Closes the temporary VAA accounts.
This is handled automatically by updateTokenPricesTx.
Raydium CLMM Oracle
Raydium CLMM oracles are currently disabled on-chain. The information below is for reference only.
Uses on-chain TWAP observations and tick arrays to compute prices. Requires twap_seconds_ago to be set (e.g., 300 for a 5-minute TWAP).
Raydium CPMM Oracle
Uses on-chain TWAP observations and vault reserves to compute prices. Requires twap_seconds_ago to be set.
Multi-Oracle Example
Configure a token with both Pyth and Raydium CPMM oracles (note: Raydium CLMM is currently disabled):
await sdk.addOrEditTokenTx(
{ vault: "<VAULT>", manager: "<MANAGER>" },
{
token_mint: "<TOKEN_MINT>",
active: true,
min_oracles_thresh: 1,
min_conf_bps: 10,
conf_thresh_bps: 300,
conf_multiplier: 1.5,
oracles: [
{
oracle_type: "pyth",
account_lut_id: 0,
account_lut_index: 0,
account: "<PYTH_PRICE_ACCOUNT>",
weight_bps: 7000,
is_required: false,
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",
},
{
oracle_type: "raydium_cpmm",
account_lut_id: 0,
account_lut_index: 1,
account: "<RAYDIUM_CPMM_POOL>",
weight_bps: 3000,
is_required: false,
conf_thresh_bps: 300,
volatility_thresh_bps: 300,
max_slippage_bps: 1500,
min_liquidity: 1000000,
staleness_thresh: 300,
staleness_conf_rate_bps: 100,
token_decimals: 9,
twap_seconds_ago: 300,
twap_secondary_seconds_ago: 60,
quote_token: "usdc",
},
],
}
);