# Fees & Oracles
Source: https://docs.symmetry.fi/concepts/fees-and-oracles
Fee structure (deposit, withdrawal, management, performance) and oracle configuration.
## 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.00 and the vault token price rises to $1.10, the performance fee applies to the $0.10 gain. If the price later drops to $0.90 and recovers to $1.05, no performance fee is charged because the price hasn't exceeded the previous $1.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](/concepts/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:
```typescript theme={null}
host_platform_params: {
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
```typescript theme={null}
const tx = await sdk.editFeesTx(
{ vault: "", 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:
```typescript theme={null}
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**
```typescript theme={null}
const tx = await sdk.withdrawVaultFeesTx({
claimer: wallet.publicKey.toBase58(),
vault: "",
});
```
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)**
```typescript theme={null}
const tx = await sdk.claimTokenFeesFromVaultTx({
claimer: wallet.publicKey.toBase58(),
withdrawVaultFees: "",
});
```
### 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
```typescript theme={null}
const fees = await sdk.fetchAllWithdrawVaultFees({ type: "vault", pubkey: "" });
const managerFees = await sdk.fetchManagerWithdrawVaultFees("");
const creatorFees = await sdk.fetchCreatorWithdrawVaultFees("");
const hostFees = await sdk.fetchHostWithdrawVaultFees("");
const symmetryFees = await sdk.fetchSymmetryWithdrawVaultFees("");
```
***
## Oracles
Each token in a vault has an oracle aggregator that computes its price from up to 4 oracle sources. This applies to both SPL and Token Extensions (`Token22`) assets.
### Oracle Types
| Type | Enum | String | Description |
| ------------ | ---- | -------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Pyth | 0 | `pyth` | Pyth Network price feeds via Hermes ([Price Feed IDs](https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids)) |
| Raydium CLMM | 1 | `raydium_clmm` | Raydium Concentrated Liquidity AMM TWAP |
| Raydium CPMM | 2 | `raydium_cpmm` | Raydium Constant Product AMM TWAP |
| LST | 3 | `lst` | Liquid Staking Token exchange rate from an SPL or Sanctum stake pool account |
| Example | 255 | `example` | Placeholder |
### 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:
```typescript theme={null}
interface OracleInput {
oracle_type: "pyth" | "raydium_clmm" | "raydium_cpmm" | "lst" | "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:
```typescript theme={null}
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)
```typescript theme={null}
{
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](https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids). During price updates, the SDK:
1. Fetches feed IDs from the on-chain price accounts.
2. Requests VAAs from the Pyth Hermes API.
3. Creates, initializes, writes, and verifies VAAs on-chain.
4. Updates the individual price feeds.
5. Runs the vault's `updateTokenPrices` instruction.
6. Closes the temporary VAA accounts.
This is handled automatically by `updateTokenPricesTx`.
### Raydium CLMM Oracle
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.
### LST Oracle
Derives a liquid staking token's price directly from its on-chain stake pool. The `account` field is the **stake pool state account** owned by either the SPL Stake Pool program (`SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy`) or the Sanctum Stake Pool program (`SP12tWFxD9oJsVWNavTTBZvMbA6gkAmxtVgxdqvyvhY`). The oracle reads `total_lamports` and `pool_token_supply` and computes:
```
price = (total_lamports / pool_token_supply) * quote_price
```
`quote_token` controls the denomination of the resulting price:
* `wsol` — price is the LST/SOL exchange rate scaled by the live WSOL price (i.e. the LST priced in USD via SOL).
* `usdc` — same shape but scaled via USDC.
* `usd` — raw exchange rate without quote-price scaling (treated as 1.0 quote).
`token_decimals` should match the LST mint's decimals (typically `9`). Because the price is derived from on-chain state, no off-chain VAA or TWAP configuration is needed — `twap_seconds_ago` and `twap_secondary_seconds_ago` can be left at `0`.
```typescript theme={null}
{
oracle_type: "lst",
account_lut_id: 0,
account_lut_index: 0,
account: "",
weight_bps: 10000,
is_required: true,
conf_thresh_bps: 100,
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: "wsol",
}
```
### Multi-Oracle Example
Configure a token with both Pyth and Raydium CLMM oracles:
```typescript theme={null}
await sdk.addOrEditTokenTx(
{ vault: "", manager: "" },
{
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: "",
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_clmm",
account_lut_id: 0,
account_lut_index: 1,
account: "",
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",
},
],
}
);
```
# Global Config
Source: https://docs.symmetry.fi/concepts/global-config
Protocol-wide configuration parameters set by the Symmetry admin.
The global config is a single on-chain account that controls protocol-wide parameters. It is managed by the protocol admin and affects all vaults. Fetch it via `symmetry.fetchGlobalConfig()`.
## Current Feature Status
| Feature | Status | Notes |
| -------------------------- | ------------ | ------------------------------------------------------------------- |
| Deposit fees | **Active** | Configurable per vault |
| Withdrawal fees | **Active** | Configurable per vault |
| Management fees | **Disabled** | Global config values set to 0. Vault-level settings have no effect. |
| Performance fees | **Disabled** | Global config values set to 0. Vault-level settings have no effect. |
| Token Extensions (Token22) | **Active** | Supported in SDK token flows and vault composition |
| Raydium CLMM oracle | **Active** | Available for oracle configuration |
| Raydium CPMM oracle | **Active** | Available for oracle configuration |
| LST oracle | **Active** | Reads SPL/Sanctum stake pool state for liquid staking tokens |
| Liquidity provision | **Disabled** | Currently disabled |
## Protocol Fee Parameters
| Parameter | 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
| Parameter | Description |
| ------------------------- | ------------------------------- |
| `max_deposit_fee_bps` | Maximum allowed deposit fee |
| `max_withdraw_fee_bps` | Maximum allowed withdrawal fee |
| `max_management_fee_bps` | Maximum allowed management fee |
| `max_performance_fee_bps` | Maximum allowed performance fee |
## Rebalance Parameters
| Parameter | Description |
| ------------------------------- | ------------------------------------------- |
| `rebalance_auction_1_timeframe` | Duration of auction stage 1 |
| `rebalance_auction_2_timeframe` | Duration of auction stage 2 |
| `rebalance_auction_3_timeframe` | Duration of auction stage 3 |
| `bounty_bond_amount` | Fixed bond amount locked alongside bounties |
## Vault Creation
| Parameter | Description |
| -------------------- | ----------------------------------------------- |
| `vault_id` (counter) | Sequential counter used for mint PDA derivation |
# Intents
Source: https://docs.symmetry.fi/concepts/intents
How vault configuration changes work through the intent system.
For SDK-facing workflows, vault configuration changes are performed via the intent system. An intent is an on-chain account that stores a proposed change to a vault's settings. This enables time-locks, scheduled changes, and bounty incentives for keeper execution.
The protocol also includes a direct private-basket settings instruction path, primarily for creator-controlled private setup flows. This path is not exposed in the SDK.
## How Intents Work
A manager calls one of the `edit*Tx` methods (e.g., `editFeesTx`, `addOrEditTokenTx`).
If modification delay is 0 AND no scheduled activation time, the intent is created and executed in the same transaction (immediate).
If there is a modification delay or scheduled activation, the intent is created on-chain and waits.
After the activation timestamp, any keeper can call `executeVaultIntentTx`. After the expiration timestamp, any keeper can call `cancelVaultIntentTx`.
## Intent Structure
```typescript theme={null}
interface FormattedIntent {
pubkey: string;
manager: string;
status: "not_active" | "active" | "reverted" | "completed";
activation_timestamp: number;
expiration_timestamp: number;
vault: string;
bounty: FormattedBounty;
task_type: FormattedTaskType;
task_data: Settings;
}
```
## Task Types
| TaskType | String | Description |
| ---------------------------------- | -------------------------------- | -------------------------------------- |
| `EditCreator` (1) | `edit_creator` | Transfer vault creator role |
| `EditManagerSettings` (2) | `edit_manager_settings` | Edit managers, weights, authorities |
| `EditFeeSettings` (3) | `edit_fee_settings` | Update fee structure |
| `EditScheduleSettings` (4) | `edit_schedule_settings` | Configure cycle timing |
| `EditAutomationSettings` (5) | `edit_automation_settings` | Configure rebalance automation |
| `EditLpSettings` (6) | `edit_lp_settings` | Configure LP settings |
| `EditMetadataSettings` (7) | `edit_metadata_settings` | Update name, symbol, URI |
| `EditDepositsSettings` (8) | `edit_deposits_settings` | Enable/disable deposits |
| `EditForceRebalanceSettings` (9) | `edit_force_rebalance_settings` | Enable/disable force rebalance |
| `EditCustomRebalanceSettings` (10) | `edit_custom_rebalance_settings` | Enable/disable custom rebalance |
| `EditAddTokenDelay` (11) | `edit_add_token_delay` | Set time-lock for adding tokens |
| `EditUpdateWeightsDelay` (12) | `edit_update_weights_delay` | Set time-lock for weight updates |
| `EditMakeDirectSwapDelay` (13) | `edit_make_direct_swap_delay` | Set time-lock for direct swaps |
| `AddToken` (14) | `add_token` | Add new token or edit oracle config |
| `UpdateWeights` (15) | `update_weights` | Change token target weights |
| `MakeDirectSwap` (16) | `make_direct_swap` | Execute a direct swap within the vault |
## Settings Types
```typescript theme={null}
{ creator: string }
```
```typescript theme={null}
{
managers: {
pubkey: string;
fee_split_weight_bps: number;
authorities: {
managers: boolean;
fees: boolean;
schedule: boolean;
automation: boolean;
lp: boolean;
metadata: boolean;
deposits: boolean;
force_rebalance: boolean;
custom_rebalance: boolean;
add_token: boolean;
update_weights: boolean;
make_direct_swap: boolean;
};
}[];
modification_delay: number;
}
```
```typescript theme={null}
{
creator_deposit_fee_bps: number;
creator_withdraw_fee_bps: number;
creator_management_fee_bps: number;
creator_performance_fee_bps: number;
managers_deposit_fee_bps: number;
managers_withdraw_fee_bps: number;
managers_management_fee_bps: number;
managers_performance_fee_bps: number;
vault_deposit_fee_bps: number;
vault_withdraw_fee_bps: number;
modification_delay: number;
}
```
Configures repeating time cycles that control when deposits, automated rebalancing, and management actions are allowed. The current position within the cycle is computed as `(current_time - cycle_start_time) % cycle_duration`. An action is allowed when the cycle position falls between its `start` and `end` offsets. If `cycle_duration` is 0, everything is always allowed. See [Schedule & Cycles](/concepts/vaults#schedule--cycles) for full documentation.
```typescript theme={null}
{
cycle_start_time: number; // unix timestamp marking the first cycle's start
cycle_duration: number; // length of each cycle in seconds (0 = no restriction)
deposits_start: number; // offset (seconds) from cycle start when deposits open
deposits_end: number; // offset (seconds) from cycle start when deposits close
automation_start: number; // offset (seconds) from cycle start when automated rebalancing opens
automation_end: number; // offset (seconds) from cycle start when automated rebalancing closes
management_start: number; // offset (seconds) from cycle start when management actions open
management_end: number; // offset (seconds) from cycle start when management actions close
modification_delay: number; // seconds — time-lock for future schedule changes
}
```
Configures automated keeper-initiated rebalancing. When `enabled`, keepers can trigger rebalances when the vault's token weights drift beyond the specified thresholds. See [Rebalancing — Vault Rebalance](/concepts/rebalancing#vault-rebalance-keeper-initiated) for the full list of conditions.
```typescript theme={null}
{
enabled: boolean; // whether automated rebalancing is allowed
rebalance_slippage_threshold_bps: number; // max overall TVL slippage allowed during the rebalance auction (bps)
per_trade_rebalance_slippage_threshold_bps: number; // max slippage allowed per individual flash swap trade (bps)
rebalance_activation_threshold_abs_bps: number; // a token must deviate by at least this % of total TVL to trigger (e.g., 500 = 5%)
rebalance_activation_threshold_rel_bps: number; // a token must deviate by at least this % relative to its own target value (e.g., 1000 = 10%)
rebalance_activation_cooldown: number; // minimum seconds between automated rebalances
modification_delay: number; // seconds — time-lock for future automation changes
}
```
Both `rebalance_activation_threshold_abs_bps` AND `rebalance_activation_threshold_rel_bps` must be exceeded by at least one token for a rebalance to be triggered — they are not independent conditions.
Configures LP (liquidity provider) mode. When `enabled`, the vault can act as a liquidity provider. `lp_threshold_bps` sets the maximum deviation from target weights before LP swaps are restricted.
```typescript theme={null}
{
enabled: boolean;
lp_threshold_bps: number;
modification_delay: number;
}
```
```typescript theme={null}
{
symbol: string;
name: string;
uri: string;
modification_delay: number;
}
```
Controls whether users can deposit into the vault. Always takes effect immediately (no modification delay).
```typescript theme={null}
{ enabled: boolean }
```
Controls whether authorized managers can trigger rebalances that bypass the normal automation checks (schedule window, cooldown, deviation threshold). When `enabled: false`, this override is disabled and all rebalances must go through normal automation conditions.
```typescript theme={null}
{
enabled: boolean;
modification_delay: number; // seconds — time-lock for future changes to this setting
}
```
Controls whether custom rebalances (type `VaultCustom`) are allowed.
```typescript theme={null}
{
enabled: boolean;
modification_delay: number; // seconds — time-lock for future changes to this setting
}
```
```typescript theme={null}
{ modification_delay: number }
```
```typescript theme={null}
{
token_mint: string;
active: boolean;
min_oracles_thresh: number;
min_conf_bps: number;
conf_thresh_bps: number;
conf_multiplier: number;
oracles: OracleInput[];
}
```
```typescript theme={null}
{
token_weights: {
mint: string;
weight_bps: number;
}[];
token_mints_hash?: number[];
}
```
```typescript theme={null}
{
from_token_mint: string;
to_token_mint: string;
amount_from: number;
amount_to: number;
}
```
## TaskContext
Every edit method takes a `TaskContext` object:
```typescript theme={null}
interface TaskContext {
vault: string;
manager: string;
activation_timestamp?: number;
expiration_timestamp?: number;
min_bounty?: number;
max_bounty?: number;
}
```
## Creating Intents
All edit methods follow the same pattern:
```typescript theme={null}
const tx = await sdk.editFeesTx(
{
vault: "",
manager: wallet.publicKey.toBase58(),
activation_timestamp: Math.floor(Date.now() / 1000) + 86400,
expiration_timestamp: Math.floor(Date.now() / 1000) + 172800,
},
{
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: 0,
}
);
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
```
## Executing Intents
After the activation timestamp, a keeper (or any user) can execute the intent:
```typescript theme={null}
const tx = await sdk.executeVaultIntentTx({
keeper: wallet.publicKey.toBase58(),
intent: "",
});
```
For `MakeDirectSwap` intents, use `executeDirectSwapVaultIntentTx` which builds the flash swap:
```typescript theme={null}
const tx = await sdk.executeDirectSwapVaultIntentTx({
keeper: wallet.publicKey.toBase58(),
intent: "",
jup_swap_ix: jupiterSwapInstruction,
jup_token_ledger_ix: jupiterTokenLedgerInstruction,
jup_address_lookup_table_addresses: [...],
});
```
## Cancelling Intents
After the expiration timestamp (or by the manager before activation):
```typescript theme={null}
const tx = await sdk.cancelVaultIntentTx({
keeper: wallet.publicKey.toBase58(),
intent: "",
});
```
## Fetching Intents
```typescript theme={null}
const intent = await sdk.fetchIntent("");
const map = await sdk.fetchMultipleIntents(["", ""]);
const all = await sdk.fetchAllIntents();
const byManager = await sdk.fetchAllIntents({ type: "manager", pubkey: "" });
const byVault = await sdk.fetchVaultIntents("");
```
## Bounty System
Each intent carries a bounty that incentivizes keepers to execute it:
```typescript theme={null}
interface FormattedBounty {
bounty_depositor: string;
bounty_mint: string;
bounty_per_price_update_task: FormattedBountySchedule;
bounty_per_task: FormattedBountySchedule;
bounty_total: number;
bounty_left: number;
}
interface FormattedBountySchedule {
min_bounty: number;
max_bounty: number;
min_bounty_until: number;
max_bounty_after: number;
}
```
The bounty scales between `min_bounty` and `max_bounty` based on how long the intent has been waiting, incentivizing faster execution.
# Protocol Overview
Source: https://docs.symmetry.fi/concepts/overview
What Symmetry is, how it works, architecture, roles, and core concepts.
Symmetry is on-chain infrastructure on Solana for creating and managing multi-token vaults with automated rebalancing. It is a protocol — not a single application. Anyone can build on top of it.
## What Symmetry Does
Symmetry allows anyone to create a vault that:
1. Holds multiple token mints (up to 100), including SPL and Token Extensions (Token22), with individually configurable target weights.
2. Mints its own vault token — holders have proportional ownership of all underlying tokens.
3. Uses multi-source oracle pricing (Pyth, Raydium CLMM, Raydium CPMM, and LST stake-pool oracles) with configurable aggregation per token.
4. Supports automated rebalancing to maintain target weights via a keeper network, with configurable deviation thresholds, cooldowns, and schedule windows that control when rebalancing is allowed.
5. Has a flexible fee structure (deposit, withdrawal, management, performance) split across creator, host platform, managers, and the protocol, with performance fees gated by a high watermark. Management and performance fees are currently disabled at the protocol level.
6. Uses an intent system for all configuration changes with optional time-locks and bounties.
## Architecture
Each vault stores token holdings (SPL and Token22, up to 100 total), target weights (basis points summing to 10,000), oracle aggregators per token, fee settings (4 tiers × 4 categories), and its own vault token mint.
Three systems interact with the vault:
Configuration changes — editing fees, updating metadata, adding tokens, changing weights, or making direct swaps.
Deposits, withdrawals, and periodic rebalances that move the vault toward target weights through auctions.
Off-chain agents that execute intents, process rebalances, update prices, and earn bounties.
## Roles
| Role | Description | How Set |
| ------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------- |
| Creator | Creates the vault. Receives creator fees. Can transfer the role. | Set at vault creation. Transferable via `editCreatorTx`. |
| Host | Platform hosting the vault UI. Receives host fees. | Set at vault creation. Immutable. |
| Managers (up to 10) | Control vault settings per authority bitmasks. Receive manager fees split by weight. | Set via `editManagersTx`. |
| Keepers | Off-chain agents that execute tasks and earn bounties. | Permissionless. Anyone can run a keeper. |
| Users | Deposit/withdraw tokens. Hold vault tokens for proportional ownership. | Permissionless. |
| Symmetry Protocol | Collects protocol-level fees. | Configured in global config by admin. |
## Core Concepts
### Vaults
A vault is an on-chain account (program-derived from the vault's token mint) that stores:
* A list of token mints (SPL and Token22) with amounts, target weights, and oracle configurations.
* Settings: fees, schedule, automation, LP, metadata, manager authorities, and more.
* Its own vault token mint — when users deposit, they receive vault tokens proportional to their contribution. When they withdraw, they burn vault tokens to receive underlying tokens.
The vault's value (TVL) is computed from live oracle prices of all held tokens. The vault token price = TVL / total supply of vault tokens.
See [Vaults](/concepts/vaults) for the full vault system documentation.
### Intents
All configuration changes to a vault go through the intent system. An intent is an on-chain account that stores a proposed change — editing fees, updating metadata, adding tokens, changing weights, or making direct swaps. Intents have:
* An activation timestamp (when the change can be applied).
* An expiration timestamp (when the intent becomes cancellable).
* A bounty (reward for the keeper who executes it).
* A modification delay (enforced per-setting time-lock).
If the relevant setting has zero modification delay AND no scheduled activation, the intent is created and executed in the same transaction.
See [Intents](/concepts/intents) for the full intent system documentation.
### Deposits, Withdrawals & Rebalances
Deposits, withdrawals, and periodic rebalances are processed through the same on-chain flow (internally called "rebalance intents"). This flow progresses through a multi-step auction:
1. **Create & Initialize** — The deposit, withdrawal, or rebalance is created on-chain.
2. **Deposit Tokens** (deposits only) — User contributes tokens into the vault.
3. **Lock Deposits** (deposits only) — Freezes contributions and starts the process.
4. **Update Prices** — Oracle prices are refreshed on-chain for all vault tokens. Done by keepers.
5. **Auction** — Three sequential auction stages. Keepers execute flash swaps (atomic withdraw → swap via Jupiter or any DEX → deposit) to settle the vault toward target weights. Pricing starts wide (using oracle confidence bands), crosses through mid-price at the midpoint, and ends at the opposite extreme — creating increasing incentive for keepers over time.
6. **Mint** (deposits) — Vault tokens are minted to the depositor.
7. **Redeem** (withdrawals) — Underlying tokens are sent to the withdrawer.
8. **Claim Bounty** — Keepers collect their bounty rewards. The rebalance intent account is closed.
See [Rebalancing](/concepts/rebalancing) for the full rebalancing documentation.
### Keepers
Keepers are off-chain agents that monitor the protocol and execute pending tasks:
* Execute configuration intents after their activation time.
* Cancel expired intents.
* Update oracle prices during rebalances.
* Execute flash swaps during auction windows.
* Mint vault tokens after deposits complete.
* Redeem tokens after withdrawals complete.
* Claim bounties after all tasks are done.
Keepers earn bounties for each task. All keeper operations are permissionless — any wallet can call the same SDK methods (`updateTokenPricesTx`, `flashSwapTx`, `mintTx`, `redeemTokensTx`, `claimBountyTx`, etc.) directly. Developers can build custom keeper bots using these methods. The SDK also ships with `KeeperMonitor` and `RebalanceHandler` as reference implementations that can be used directly or as a starting point.
See [Keeper Infrastructure](/guides/keeper) for setup and operation.
## Authority Bitmasks
Each vault setting has an authority bitmask that controls which managers (and the creator) can modify it. The bitmask is a u16 where bits 0–9 correspond to manager slots and **bit 10 corresponds to the creator**. If bit N is set, the entity at that index has authority to modify the setting.
Authority categories:
| Category | Controls |
| ------------------ | -------------------------------------------------- |
| `managers` | Edit manager list, weights, and authority bitmasks |
| `fees` | Edit fee settings |
| `schedule` | Edit schedule settings |
| `automation` | Edit automation settings |
| `lp` | Edit LP settings |
| `metadata` | Edit name, symbol, URI |
| `deposits` | Enable/disable deposits |
| `force_rebalance` | Enable/disable force rebalance |
| `custom_rebalance` | Enable/disable custom rebalance |
| `add_token` | Add tokens or edit oracle configs |
| `update_weights` | Change token target weights |
| `make_direct_swap` | Execute direct swaps within the vault |
## Modification Delays
Most vault settings have a configurable modification delay (in seconds). When a modification delay is set:
1. The intent is created on-chain but NOT immediately executed.
2. After the delay period elapses, a keeper can execute the intent.
3. This provides a safety window where users can see upcoming changes and exit the vault if they disagree.
Settings with zero delay are executed immediately in the same transaction as the intent creation.
## Transaction Model
All SDK methods that modify on-chain state return a `TxPayloadBatchSequence`. This is a sequence of transaction batches:
* **Batches** are sent sequentially (batch 0 must confirm before batch 1 is sent).
* **Transactions within a batch** are sent in parallel.
This model handles complex multi-step operations like vault creation (which needs account creation, resizing, and initialization in sequence) or rebalance price updates (which need Pyth VAA creation, verification, and feed updates in sequence).
```typescript theme={null}
const signatures = await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
simulateTransactions: false,
});
```
## Networks
| Network | USDC Mint | WSOL Mint |
| --------- | ---------------------------------------------- | --------------------------------------------- |
| `mainnet` | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | `So11111111111111111111111111111111111111112` |
| `devnet` | `USDCoctVLVnvTXBEuP9s8hntucdJokbo17RwHuNXemT` | `So11111111111111111111111111111111111111112` |
# Rebalancing
Source: https://docs.symmetry.fi/concepts/rebalancing
Deposits, withdrawals, vault rebalances, the auction flow, and flash swaps.
Rebalancing is the mechanism by which vaults process deposits, withdrawals, and periodic rebalances to maintain target weights. All three operations use the same rebalance intent flow.
## Rebalance Types
| Type | Enum | Description |
| ----------- | ---- | ---------------------------------------------------- |
| Deposit | 0 | User deposits tokens → receives vault tokens |
| Withdraw | 1 | User burns vault tokens → receives underlying tokens |
| Vault | 2 | Keeper-initiated rebalance to restore target weights |
| VaultCustom | 3 | Custom vault rebalance |
## Lifecycle
Each rebalance type follows a similar on-chain flow, but with type-specific stages:
**Deposit flow:**
```
[Create] → [Deposit Tokens] → [Lock Deposits] → [Update Prices]
→ [Auction 1] → [Auction 2] → [Auction 3]
→ [Mint] → [Claim Bounty] → [Close Account]
```
**Withdraw flow:**
```
[Create] → [Update Prices]
→ [Auction 1] → [Auction 2] → [Auction 3]
→ [Redeem] → [Claim Bounty] → [Close Account]
```
**Vault / VaultCustom flow:**
```
[Create] → [Update Prices]
→ [Auction 1] → [Auction 2] → [Auction 3]
→ [Claim Bounty] → [Close Account]
```
| Action | Enum | Description |
| ---------------- | ---- | ------------------------------------------- |
| `not_active` | 0 | Intent not yet initialized |
| `deposit_tokens` | 1 | User is depositing tokens (deposits only) |
| `update_prices` | 3 | Oracle prices need to be refreshed |
| `auction` | 4 | Auction phase — keepers execute flash swaps |
Mint, redeem, and claim bounty are terminal task stages — they do not appear as separate `current_action` enum values but are handled after the auction phase completes.
### Obtaining a Rebalance Intent Key
Many SDK methods require a `rebalance_intent` pubkey. You can obtain it in two ways:
1. **Deterministic PDA** — The rebalance intent address is derived from `[REBALANCE_INTENT_SEED, vault_key, owner_key]`. Use this when you know both the vault and the owner.
2. **Fetch from on-chain** — Query active intents:
```typescript theme={null}
const ownerIntents = await sdk.fetchOwnerRebalanceIntents("");
const vaultIntents = await sdk.fetchVaultRebalanceIntents("");
```
## Deposits
Each user can have only **one** active deposit or withdrawal per vault at a time. Starting a new deposit while a previous one is still processing will fail because the rebalance intent account (a PDA derived from the vault and user addresses) already exists.
### Step 1: Create Deposit Intent and Deposit Tokens
```typescript theme={null}
// Amounts are raw (smallest units): SOL = 9 decimals, USDC = 6 decimals
const tx: TxPayloadBatchSequence = await sdk.buyVaultTx({
buyer: wallet.publicKey.toBase58(),
vault_mint: "",
contributions: [
{ mint: "So11111111111111111111111111111111111111112", amount: 1_000_000_000 }, // 1 SOL
{ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: 100_000_000 }, // 100 USDC
],
rebalance_slippage_bps: 100,
per_trade_rebalance_slippage_bps: 100,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
```
This deposits the contributed tokens and starts the deposit process.
The `contributions` array specifies which tokens to deposit and how much. **All amounts are raw values in the token's smallest unit** (e.g., lamports for SOL, 10^6 units for USDC). Users can contribute any supported token mints (including Token Extensions/Token22) — they do NOT need to match the vault's composition. The rebalance auction will swap tokens as needed.
The `vault_mint` parameter is the vault's token mint address (not the vault account address).
### Step 2 (Optional): Deposit More Tokens
Before locking, users can add more contributions:
```typescript theme={null}
const tx = await sdk.depositTokensTx({
buyer: wallet.publicKey.toBase58(),
contributions: [{ mint: "", amount: 500_000_000 }],
rebalance_intent: "",
});
```
### Step 3: Lock Deposits
Locking freezes contributions and starts the rebalance process:
```typescript theme={null}
const tx = await sdk.lockDepositsTx({
buyer: wallet.publicKey.toBase58(),
vault_mint: "",
});
```
After locking, the intent moves to `update_prices`. From here, keepers typically take over — but users can execute any of these steps themselves using the same SDK methods.
### Steps 4–7: Processing
These steps are typically handled by keepers automatically, but any user can call these methods directly:
1. **Update Prices** — `updateTokenPricesTx` refreshes oracle prices.
2. **Auctions** — Three auction windows where flash swaps are executed via `flashSwapTx`.
3. **Mint** — `mintTx` mints vault tokens to the depositor.
4. **Claim Bounty** — `claimBountyTx` distributes rewards and closes the account.
Any wallet may execute these stage methods directly; keeper is an actor role, not a privilege role.
After vault tokens are minted, any deposited tokens not consumed during minting are returned to the depositor. This happens automatically: the rebalance intent transitions to a withdrawal phase. Since the depositor's contributed tokens are marked as "keep tokens," the return skips the auction entirely and proceeds directly to redemption.
## Withdrawals
```typescript theme={null}
const tx: TxPayloadBatchSequence = await sdk.sellVaultTx({
seller: wallet.publicKey.toBase58(),
vault_mint: "",
withdraw_amount: 1_000_000, // raw vault token amount to burn
keep_tokens: [],
rebalance_slippage_bps: 100,
per_trade_rebalance_slippage_bps: 100,
});
```
When the vault has **performance fees** configured (any of host/creator/managers performance fee > 0), the withdrawal follows a deferred path:
1. The vault token burn amount and fee calculations are recorded but token amounts are **not** immediately subtracted from the vault.
2. The vault's `active_withdraws` counter is incremented.
3. During the `updateTokenPricesTx` step, performance fees are calculated, token amounts are subtracted from the vault, and `supply_outstanding` is adjusted.
This means withdrawals in vaults with performance fees cannot proceed concurrently with pending management intents (`active_managements` must be 0).
### `keep_tokens` Behavior
The `keep_tokens` parameter controls which tokens the user receives directly without going through auctions:
* **Empty (`[]`)** — Full rebalance flow. Keepers update prices, run 3 auction windows to swap tokens toward a single output, then redeem. This is the slowest path.
* **Partial (`[mint1, mint2]`)** — The specified tokens are sent directly to the user. The remaining tokens still go through price updates and auctions.
* **All vault mints** — **Fast withdrawal.** When every token in the vault's composition (including `active: false` slots) is passed, the protocol skips price updates and auctions entirely. The intent goes directly to the redeem stage. The user receives their proportional share of each underlying token. This still requires two transactions: `sellVaultTx` to create the intent, then `redeemTokensTx` to transfer the tokens. The user can call `redeemTokensTx` themselves — no need to wait for a keeper.
### Fast Withdrawal
When every token in the vault's composition is passed in `keep_tokens`, the protocol skips price updates and auctions. The intent goes directly to the redeem stage. This is two transactions: `sellVaultTx` then `redeemTokensTx`.
```typescript theme={null}
const vault: Vault = await sdk.fetchVault("");
const allMints: string[] = vault.formatted!.composition.map(asset => asset.mint);
const sellTx: TxPayloadBatchSequence = await sdk.sellVaultTx({
seller: wallet.publicKey.toBase58(),
vault_mint: vault.formatted!.mint,
withdraw_amount: 1_000_000,
keep_tokens: allMints,
rebalance_slippage_bps: 100,
});
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: sellTx, wallet });
const redeemTx: TxPayloadBatchSequence = await sdk.redeemTokensTx({
keeper: wallet.publicKey.toBase58(),
rebalance_intent: "",
});
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: redeemTx, wallet });
```
The `keeper` parameter in `redeemTokensTx` is just the signing wallet — any user can call this directly.
### Standard Withdrawal Flow
After creating the withdrawal intent, keepers typically process it — but users can also execute each step themselves:
1. **Update Prices** (`updateTokenPricesTx`)
2. **Auctions** (`flashSwapTx`)
3. **Redeem Tokens** (`redeemTokensTx`)
4. **Claim Bounty** (`claimBountyTx`)
Any wallet may execute these stage methods directly; keeper is an actor role, not a privilege role.
## Vault Rebalance (Keeper-Initiated)
Keepers can trigger a rebalance to bring the vault back to its target weights. A rebalance is only allowed when **all** of the following conditions are met:
1. **Automation is enabled** — `automation_settings.enabled` must be `true` (configured via `editAutomationTx`).
2. **No active rebalance** — the vault must not already have a deposit, withdrawal, or rebalance in progress.
3. **Bounty balance** — the vault must have bounty funds to pay the keeper (added via `addBountyTx`).
4. **Within the automation window** — the current time must fall within the vault's schedule automation window (`automation_start` to `automation_end` within the cycle). See [Schedule & Cycles](/concepts/vaults#schedule--cycles) for details.
5. **Cooldown elapsed** — enough time must have passed since the last automated rebalance (`rebalance_activation_cooldown` seconds).
6. **Threshold exceeded** — at least one **non-bounty** token must have drifted beyond **both** the absolute and relative deviation thresholds. The bounty token (typically WSOL) is excluded from this check.
* `rebalance_activation_threshold_abs_bps` — minimum deviation as a percentage of total vault value (e.g., 500 = 5%). Computed as `|actual_value - target_value| / vault_tvl`.
* `rebalance_activation_threshold_rel_bps` — minimum deviation relative to the token's own target (e.g., 1000 = 10%). Computed as `|actual_value - target_value| / max(actual_value, target_value)`.
The `isRebalanceRequired()` utility checks all of these conditions:
```typescript theme={null}
import { isRebalanceRequired } from "@symmetry-hq/sdk";
const needed: boolean = await isRebalanceRequired(vault, connection);
if (needed) {
const tx: TxPayloadBatchSequence = await sdk.rebalanceVaultTx({
keeper: wallet.publicKey.toBase58(),
vault_mint: "",
rebalance_slippage_bps: 100, // 1% overall slippage tolerance
per_trade_rebalance_slippage_bps: 100, // 1% per-trade slippage
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
}
```
These thresholds are configured via `editAutomationTx`:
```typescript theme={null}
const tx = await sdk.editAutomationTx(
{ vault: "", manager: wallet.publicKey.toBase58() },
{
enabled: true,
rebalance_slippage_threshold_bps: 100, // 1% max slippage during auctions
per_trade_rebalance_slippage_threshold_bps: 100, // 1% per-trade slippage
rebalance_activation_threshold_abs_bps: 500, // 5% of TVL absolute deviation triggers rebalance
rebalance_activation_threshold_rel_bps: 1000, // 10% relative deviation triggers rebalance
rebalance_activation_cooldown: 3600, // minimum 1 hour between rebalances
modification_delay: 86400, // 24h delay for future automation changes
}
);
```
## Auction System
After price updates, the rebalance enters **three sequential auction stages**. Each stage has a fixed duration configured in the protocol's global config (`rebalance_auction_1_timeframe`, `rebalance_auction_2_timeframe`, `rebalance_auction_3_timeframe`). During each stage, keepers can execute flash swaps to move the vault toward its target weights.
### Auction Pricing
The vault uses **oracle confidence bands** to create a Dutch-auction-style pricing curve that crosses through fair value, incentivizing timely execution:
* **At auction start**: The vault sells tokens at `price + confidence` (expensive for keepers) and buys tokens at `price - confidence` (cheap for keepers). This is the widest unfavorable spread.
* **Over time**: The spread narrows linearly toward mid-price. The price delta follows: `conf × 2 × time_elapsed / auction_duration`.
* **At the midpoint**: Both buy and sell prices converge to the oracle mid-price. The spread is zero.
* **After the midpoint**: The prices cross through mid-price and the vault begins offering better-than-market rates — selling below mid-price and buying above mid-price. This creates increasing profit opportunity for keepers.
* **At auction end**: Sell price reaches `price - confidence` and buy price reaches `price + confidence` — the maximum favorable spread for keepers.
This crossing mechanism means keepers face a trade-off: executing early gets worse pricing but less competition; waiting past the midpoint gets better pricing but risks another keeper taking the swap first. Each new auction stage resets this convergence.
### After Auctions End
Once all three auction stages complete:
* **Deposits**: Vault tokens are minted to the depositor via `mintTx`.
* **Withdrawals**: Underlying tokens are sent to the withdrawer via `redeemTokensTx`.
* **Vault rebalances**: The rebalance completes directly.
After minting or redeeming is complete, the bounty is claimed via `claimBountyTx`, which distributes bounty rewards to all keepers who completed tasks during the rebalance (proportional to the tasks they performed), awards a task bounty to the caller, returns unused bounty and the bounty bond to the depositor, and closes the rebalance intent account.
### Flash Swaps
A flash swap is an atomic operation within a single transaction:
1. **Flash Withdraw** — Tokens are withdrawn from the vault to the keeper.
2. **Jupiter Swap** — Keeper swaps the tokens via Jupiter (or any other DEX).
3. **Flash Deposit** — Keeper deposits the swapped tokens back into the vault.
The vault gives the keeper `mint_out` tokens and expects `mint_in` tokens back. The keeper profits from any spread between the vault's auction price and the market price.
```typescript theme={null}
import { getSwapPairs, getJupTokenLedgerAndSwapInstructions } from "@symmetry-hq/sdk";
const pairs: SwapPair[] = getSwapPairs(rebalanceIntent.chain_data, vault);
for (const pair of pairs) {
const jupResult = await getJupTokenLedgerAndSwapInstructions({
keeper: wallet.publicKey,
vaultMintIn: new PublicKey(pair.inMint),
vaultMintOut: new PublicKey(pair.outMint),
vaultAmountIn: pair.inAmount,
vaultAmountOut: pair.outAmount,
swapMode: "ioc",
apiKey: "",
maxJupAccounts: 64,
});
const tx: TxPayloadBatchSequence = await sdk.flashSwapTx({
keeper: wallet.publicKey.toBase58(),
vault: "",
rebalance_intent: "",
mint_in: pair.inMint, // token the vault receives
mint_out: pair.outMint, // token the vault gives
amount_in: pair.inAmount, // raw amount deposited to vault
amount_out: pair.outAmount, // raw amount withdrawn from vault
mode: 2, // IOC (immediate-or-cancel)
jup_token_ledger_ix: jupResult.tokenLedgerInstruction,
jup_swap_ix: jupResult.swapInstruction,
jup_address_lookup_table_addresses: jupResult.addressLookupTableAddresses,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
}
```
### Flash Swap Parameters
| Parameter | Description |
| ------------------------------------ | ------------------------------------------------------------ |
| `keeper` | Keeper's public key |
| `vault` | Vault account public key |
| `rebalance_intent` | Rebalance intent public key (for rebalance swaps) |
| `intent` | Intent public key (for direct swap intents) |
| `mint_in` | Token the vault receives (keeper deposits this) |
| `mint_out` | Token the vault gives (keeper receives this) |
| `amount_in` | Amount of `mint_in` to deposit |
| `amount_out` | Amount of `mint_out` to withdraw |
| `mode` | 0 = exact\_in, 1 = exact\_out, 2 = IOC (immediate-or-cancel) |
| `jup_token_ledger_ix` | Optional: Jupiter token ledger instruction |
| `jup_swap_ix` | Optional: Jupiter swap instruction |
| `jup_address_lookup_table_addresses` | Optional: Jupiter ALT addresses |
### Swap Pairs
`getSwapPairs(rebalanceIntent, vault)` computes all valid swap pairs from the current auction state:
```typescript theme={null}
{
inMint: string;
outMint: string;
inAmount: number;
outAmount: number;
value: number;
}
```
Pairs with `value < 0.005` are typically skipped as not worth the transaction cost.
## Minting Vault Tokens
After auction windows close for a deposit rebalance:
```typescript theme={null}
const tx: TxPayloadBatchSequence = await sdk.mintTx({
keeper: wallet.publicKey.toBase58(), // signer — any wallet can call this
rebalance_intent: "",
});
```
This mints vault tokens to the depositor proportional to the value of their contribution minus fees.
## Redeeming Tokens
After auction windows close for a withdrawal rebalance:
```typescript theme={null}
const tx: TxPayloadBatchSequence = await sdk.redeemTokensTx({
keeper: wallet.publicKey.toBase58(), // signer — any wallet can call this
rebalance_intent: "",
});
```
This sends the underlying tokens to the withdrawer.
The withdrawer must have existing Associated Token Accounts (ATAs) for all tokens being redeemed. If an ATA doesn't exist for a token and the transaction is submitted by a keeper (not the withdrawer themselves), that token will be **skipped** during redemption. Create ATAs for all expected tokens before the redemption step.
## Claiming Bounty
After minting or redeeming is complete:
```typescript theme={null}
const tx: TxPayloadBatchSequence = await sdk.claimBountyTx({
keeper: wallet.publicKey.toBase58(), // signer — any wallet can call this
rebalance_intent: "",
});
```
This distributes earned bounties to all keepers who completed tasks during the rebalance, awards a task bounty to the caller, returns unused bounty to the depositor, and closes the rebalance intent account.
## Price Updates During Rebalance
Keepers must update oracle prices before the auction can begin:
```typescript theme={null}
const tx = await sdk.updateTokenPricesTx({
keeper: wallet.publicKey.toBase58(),
vault: "",
rebalance_intent: "",
});
```
This handles all the complexity of Pyth VAA creation, verification, and feed updates automatically. The batch layout:
1. Create and initialize Pyth VAAs
2. Write and verify VAAs
3. Update individual price feeds
4. Update token prices in the vault + close VAA accounts
### Standalone Pyth Price Update
To update Pyth prices without a rebalance context:
```typescript theme={null}
const tx: TxPayloadBatchSequence = await sdk.updatePythPriceFeedsTx({
keeper: wallet.publicKey.toBase58(),
accounts: ["", ""],
});
```
## Cancelling a Rebalance
```typescript theme={null}
const tx = await sdk.cancelRebalanceIntentTx({
keeper: wallet.publicKey.toBase58(),
rebalance_intent: "",
});
```
## Fetching Rebalance Intents
```typescript theme={null}
const ri = await sdk.fetchRebalanceIntent("");
const riMap = await sdk.fetchMultipleRebalanceIntents(["", ""]);
const all = await sdk.fetchAllRebalanceIntents();
const byOwner = await sdk.fetchOwnerRebalanceIntents("");
const byVault = await sdk.fetchVaultRebalanceIntents("");
```
## Checking If Rebalance Is Needed
```typescript theme={null}
import { isRebalanceRequired } from "@symmetry-hq/sdk";
const needed = await isRebalanceRequired(vault, connection);
// Returns true when all conditions are met: automation enabled, no active rebalance,
// bounty available, within schedule window, cooldown elapsed, and threshold exceeded.
```
## Concurrent Operations
Deposits and withdrawals can proceed while a vault rebalance is in progress. When a deposit mints tokens or a withdrawal redeems tokens during an active vault rebalance, the vault rebalance intent's token amounts and target amounts are automatically updated to reflect the changed vault composition. This prevents stale data in the rebalance auction.
However, vault rebalances cannot run concurrently with each other (only one vault rebalance can exist at a time), and they require `active_managements == 0` (no pending configuration changes).
## Rebalance Intent Data Structure
```typescript theme={null}
interface UIRebalanceIntent {
rebalance_type: "deposit" | "withdraw" | "vault" | "vault_custom";
deposit_data: DepositData | null;
price_updates_data: PriceUpdatesData | null;
auction_data: AuctionData | null;
mint_data: MintData | null;
redeem_data: RedeemData | null;
claim_bounty_data: ClaimBountyData | null;
formatted_data: FormattedRebalanceIntent;
chain_data: RebalanceIntent;
}
```
# Vaults
Source: https://docs.symmetry.fi/concepts/vaults
Vault structure, composition, settings, creation, and configuration.
A vault is the fundamental unit of the Symmetry protocol. It is an on-chain account that holds a configurable set of token mints (SPL and Token Extensions/Token22) with target weights, and mints its own vault token representing proportional ownership.
## Vault Account Structure
Every vault has these core properties:
| Field | Type | Description |
| -------------------- | ------------- | ----------------------------------------------------------------------------------- |
| `version` | u8 | Account version |
| `ownAddress` | PublicKey | The vault's on-chain address (PDA derived from `["basket", vault_mint]`) |
| `mint` | PublicKey | The vault's token mint (PDA derived from `["mint", vault_id_u64]`) |
| `supplyOutstanding` | u64 | Total supply of vault tokens currently in circulation |
| `creation_timestamp` | u64 | Unix timestamp of when the vault was created. Set once at creation, never modified. |
| `settings` | VaultSettings | All vault configuration (fees, managers, schedule, automation, etc.) |
| `accumulatedFees` | VaultFees | Accumulated but unclaimed fees (symmetry, creator, host, managers) |
| `lookupTables` | LookupTables | Address lookup tables for oracle accounts (2 active, 2 temp) |
| `numTokens` | u8 | Number of active tokens in the vault |
| `composition` | Asset\[100] | Array of up to 100 token positions |
## Vault Token
Each vault mints its own vault 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:
| Field | Type | Description |
| ------------------ | ---------------- | ---------------------------------------------------------------------------- |
| `mint` | PublicKey | Token mint address (supports SPL and Token22 assets in composition) |
| `amount` | u64 | Current token amount held (in smallest units) |
| `weight` | u16 | Target weight in basis points. All active weights must sum to 10,000 (100%). |
| `active` | u8 | Whether this token slot is active (1) or inactive (0) |
| `oracleAggregator` | OracleAggregator | Oracle configuration for pricing this token |
Vault composition supports both classic SPL mints and Token Extensions (`Token22`) mints.
When prices are loaded via `loadVaultPrice()`, each asset also gets:
* `price` — current oracle price
* `value` — computed USD value of the position
## Vault Types
| Type | Value | Description |
| ------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Private | 0 | Default. 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. |
| Public | 1 | Vault 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:
```typescript theme={null}
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:
```json theme={null}
{
"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"
}
```
| Field | Required | Description |
| ------------- | -------- | ----------------------------------------------------------------------------------- |
| `name` | Yes | The vault token name. Should match the `name` passed to `createVaultTx`. |
| `symbol` | Yes | The vault token symbol/ticker. Should match the `symbol` passed to `createVaultTx`. |
| `description` | Yes | A human-readable description of the vault, displayed on frontends. |
| `image` | Yes | URL to the vault token's image (icon/logo), used in wallets and token lists. |
| `cover` | Yes | URL 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:
```json theme={null}
{
"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
```typescript theme={null}
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_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](https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids).
```typescript theme={null}
const tx = await sdk.addOrEditTokenTx(
{
vault: "",
manager: "",
},
{
token_mint: "",
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: "",
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:
```typescript theme={null}
const tx = await sdk.updateWeightsTx(
{
vault: "",
manager: "",
},
{
token_weights: [
{ mint: "", weight_bps: 5000 },
{ mint: "", weight_bps: 3000 },
{ mint: "", weight_bps: 2000 },
],
}
);
```
## Fetching Vaults
```typescript theme={null}
const vault = await sdk.fetchVault("");
const vaultsMap = await sdk.fetchMultipleVaults(["", ""]);
const allVaults = await sdk.fetchAllVaults();
const creatorVaults = await sdk.fetchCreatedVaults("");
const hostedVaults = await sdk.fetchHostedVaults("");
const managedVaults = await sdk.fetchManagedVaults("");
const mintMap = await sdk.fetchVaultsFromMints(["", ""]);
// Derive vault address from mint without an RPC call
const addrMap = await sdk.deriveVaultsByMints([""]);
```
## Loading Prices
Calling `fetchVault` returns the vault without live prices. To get current prices and TVL:
```typescript theme={null}
let vault = await sdk.fetchVault("");
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:
| Setting | Key Fields | Delay Field |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| Creator | `creator` pubkey | No delay (immediate) |
| Managers | `managers[]` (pubkey, fee\_split\_weight, authorities) | `modification_delay` |
| Fees | deposit/withdraw fees for creator+managers, vault deposit/withdraw fees (management/performance fees currently disabled in global config) | `modification_delay` |
| Schedule | cycle timing, deposit/automation/management windows | `modification_delay` |
| Automation | enabled, slippage/activation thresholds, cooldown | `modification_delay` |
| LP | enabled, `lp_threshold_bps` | `modification_delay` |
| Metadata | name, symbol, URI | `modification_delay` |
| Deposits | enabled | No delay (immediate) |
| Force Rebalance | enabled | `modification_delay` |
| Custom Rebalance | enabled | `modification_delay` |
| Add Token Delay | modification delay for adding tokens | `modification_delay` |
| Update Weights Delay | modification delay for weight changes | `modification_delay` |
| Direct Swap Delay | modification delay for direct swaps | `modification_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:
| Counter | Field | Description |
| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Active Rebalance | `active_rebalance` | Number of vault-level rebalances currently in progress. When > 0, new keeper-initiated rebalances are blocked (`isRebalanceRequired()` returns false). |
| Active Withdrawals | `active_withdraws` | Number of withdrawals currently being processed. |
| Active Managements | `active_managements` | Number 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:
| Window | Fields | What it gates |
| ---------- | ------------------------------------ | ------------------------------------- |
| Deposits | `deposits_start`, `deposits_end` | When users can deposit into the vault |
| Automation | `automation_start`, `automation_end` | When automated operations can run |
| Management | `management_start`, `management_end` | When 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
```typescript theme={null}
const tx = await sdk.editScheduleTx(
{ vault: "", 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:
```typescript theme={null}
{
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
| Constant | Value |
| ----------------------- | --------------------------------- |
| Max tokens per vault | 100 |
| Max managers per vault | 10 |
| Max oracles per token | 4 |
| Max accounts per oracle | 4 |
| Weight unit | Basis points (bps), 10,000 = 100% |
| Vault token decimals | 6 |
| Symbol max length | 10 characters |
| Name max length | 32 characters |
| URI max length | 200 characters |
# Integration Examples
Source: https://docs.symmetry.fi/guides/examples
End-to-end code examples for common Symmetry integration scenarios.
## Setup
All examples use this shared setup:
```typescript theme={null}
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import {
SymmetryCore,
KeeperMonitor,
RebalanceHandler,
getJupTokenLedgerAndSwapInstructions,
getSwapPairs,
isRebalanceRequired,
} from "@symmetry-hq/sdk";
function createWallet(keypair: Keypair) {
return {
publicKey: keypair.publicKey,
signTransaction: async (tx: T): Promise => {
(tx as any).sign([keypair]);
return tx;
},
signAllTransactions: async (txs: T[]): Promise => {
txs.forEach((tx: any) => tx.sign([keypair]));
return txs;
},
payer: keypair,
};
}
const connection = new Connection("https://api.mainnet-beta.solana.com");
const keypair = Keypair.fromSecretKey(/* your secret key bytes */);
const wallet = createWallet(keypair);
const sdk = new SymmetryCore({
connection,
network: "mainnet",
priorityFee: 50_000,
});
```
Examples work with both SPL and Token Extensions (`Token22`) mints where token mint inputs are used.
***
## Create a Vault with Tokens
Creates a 2-token vault with SOL (50%) and USDC (50%).
The `metadata_uri` must point to a JSON file with the vault's metadata. The JSON should contain at minimum `name`, `symbol`, `description`, `image`, and `cover` fields. You can add any extra fields (website, social links, etc.) for your own integrations to consume.
Example metadata JSON at the URI:
```json theme={null}
{
"name": "Diversified Index",
"symbol": "DIVX",
"description": "A diversified vault tracking SOL, USDC, and other tokens.",
"image": "https://arweave.net/your-token-image-url",
"cover": "https://arweave.net/your-cover-image-url"
}
```
```typescript theme={null}
const vaultResult: VaultCreationTx = await sdk.createVaultTx({
creator: wallet.publicKey.toBase58(),
start_price: "1.0",
name: "Diversified Index",
symbol: "DIVX",
metadata_uri: "https://arweave.net/your-metadata-json", // URL to JSON with name, symbol, description, image, cover
host_platform_params: {
host_pubkey: wallet.publicKey.toBase58(),
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:", vaultResult.mint);
console.log("Vault account:", vaultResult.vault);
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: vaultResult,
wallet,
});
// Add SOL with Pyth oracle (find price feed IDs at https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids)
const addSolTx: TxPayloadBatchSequence = await sdk.addOrEditTokenTx(
{ vault: vaultResult.vault, manager: wallet.publicKey.toBase58() },
{
token_mint: "So11111111111111111111111111111111111111112",
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: "7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE",
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: addSolTx, wallet });
const addUsdcTx: TxPayloadBatchSequence = await sdk.addOrEditTokenTx(
{ vault: vaultResult.vault, manager: wallet.publicKey.toBase58() },
{
token_mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
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: "Dpw1EAVrSB1ibxiDQyTAW6Zip3J4Btk2x4SgApQCeFbX",
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: 6,
twap_seconds_ago: 0,
twap_secondary_seconds_ago: 0,
quote_token: "usd",
}],
}
);
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: addUsdcTx, wallet });
const weightsTx: TxPayloadBatchSequence = await sdk.updateWeightsTx(
{ vault: vaultResult.vault, manager: wallet.publicKey.toBase58() },
{
token_weights: [
{ mint: "So11111111111111111111111111111111111111112", weight_bps: 5000 },
{ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", weight_bps: 5000 },
],
}
);
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: weightsTx, wallet });
```
***
## Deposit into a Vault
```typescript theme={null}
const VAULT_MINT = "";
// Amounts are raw (smallest units): SOL = 9 decimals, USDC = 6 decimals
const buyTx: TxPayloadBatchSequence = await sdk.buyVaultTx({
buyer: wallet.publicKey.toBase58(),
vault_mint: VAULT_MINT,
contributions: [
{ mint: "So11111111111111111111111111111111111111112", amount: 500_000_000 }, // 0.5 SOL
{ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: 50_000_000 }, // 50 USDC
],
rebalance_slippage_bps: 100,
per_trade_rebalance_slippage_bps: 100,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: buyTx,
wallet,
});
const lockTx = await sdk.lockDepositsTx({
buyer: wallet.publicKey.toBase58(),
vault_mint: VAULT_MINT,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: lockTx,
wallet,
});
```
***
## Standard Withdrawal
```typescript theme={null}
const sellTx: TxPayloadBatchSequence = await sdk.sellVaultTx({
seller: wallet.publicKey.toBase58(),
vault_mint: "",
withdraw_amount: 1_000_000, // raw vault token amount to burn
keep_tokens: [],
rebalance_slippage_bps: 100,
per_trade_rebalance_slippage_bps: 100,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: sellTx,
wallet,
});
```
### Withdraw and Keep Specific Tokens
```typescript theme={null}
const sellTx: TxPayloadBatchSequence = await sdk.sellVaultTx({
seller: wallet.publicKey.toBase58(),
vault_mint: VAULT_MINT,
withdraw_amount: 1_000_000,
keep_tokens: ["So11111111111111111111111111111111111111112"],
rebalance_slippage_bps: 100,
per_trade_rebalance_slippage_bps: 100,
});
```
***
## Fast Withdrawal
Pass every mint from `vault.composition` to skip auctions:
```typescript theme={null}
const vault = await sdk.fetchVault("");
const allMints = vault.formatted!.composition.map(asset => asset.mint);
const sellTx = await sdk.sellVaultTx({
seller: wallet.publicKey.toBase58(),
vault_mint: vault.formatted!.mint,
withdraw_amount: 1_000_000,
keep_tokens: allMints,
rebalance_slippage_bps: 100,
per_trade_rebalance_slippage_bps: 100,
});
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: sellTx, wallet });
const redeemTx = await sdk.redeemTokensTx({
keeper: wallet.publicKey.toBase58(),
rebalance_intent: "",
});
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: redeemTx, wallet });
```
***
## Read Vault Data
```typescript theme={null}
let vault = await sdk.fetchVault("");
vault = await sdk.loadVaultPrice(vault);
const info: FormattedVault = vault.formatted!;
console.log("Name:", info.name);
console.log("Symbol:", info.symbol);
console.log("TVL:", vault.tvl?.toString());
console.log("Token Price:", vault.price?.toString());
console.log("Supply:", info.supply_outstanding);
console.log("Creator:", info.creator);
console.log("Host:", info.host);
console.log("\nComposition:");
for (const asset of info.composition) {
if (!asset.active) continue;
console.log(` ${asset.mint}: weight=${asset.weight / 100}%, amount=${asset.amount}`);
}
console.log("\nFee Settings:");
const fees = info.fee_settings;
console.log(` Creator deposit: ${fees.creator_deposit_fee_bps / 100}%`);
console.log(` Creator withdrawal: ${fees.creator_withdraw_fee_bps / 100}%`);
console.log(` Creator management: ${fees.creator_management_fee_bps / 100}%`);
console.log(` Creator performance: ${fees.creator_performance_fee_bps / 100}%`);
console.log("\nAutomation:", info.automation_settings.enabled);
console.log("Deposits enabled:", info.deposits_settings.enabled);
```
***
## List All Vaults with Prices
```typescript theme={null}
const vaults = await sdk.fetchAllVaults();
for (const vault of vaults) {
try {
const priced = await sdk.loadVaultPrice(vault);
console.log(
`${priced.formatted!.name} (${priced.formatted!.symbol})` +
` | TVL: $${priced.tvl?.toFixed(2)}` +
` | Price: $${priced.price?.toFixed(6)}` +
` | Tokens: ${priced.formatted!.composition.filter(a => a.active).length}`
);
} catch (e) {
console.log(`${vault.formatted!.name}: price load failed`);
}
}
```
***
## Update Vault Fees
```typescript theme={null}
const tx = await sdk.editFeesTx(
{
vault: "",
manager: wallet.publicKey.toBase58(),
},
{
creator_deposit_fee_bps: 25,
creator_withdraw_fee_bps: 25,
creator_management_fee_bps: 200, // currently disabled in global config
creator_performance_fee_bps: 1000, // 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: 10,
vault_withdraw_fee_bps: 10,
modification_delay: 86400,
}
);
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet });
```
***
## Run a Keeper Bot
```typescript theme={null}
const keeper = new KeeperMonitor({
wallet,
connection,
network: "mainnet",
jupiterApiKey: "",
maxAllowedAccounts: 64,
priorityFee: 50_000,
simulateTransactions: false,
});
// Option A: Run indefinitely
while (true) {
try {
await keeper.update();
} catch (e) {
console.error("Keeper update error:", e);
}
await new Promise(r => setTimeout(r, 10_000));
}
// Option B: Run for a fixed duration
await keeper.run(600);
```
***
## Handle a Single Rebalance
```typescript theme={null}
await RebalanceHandler.run({
intentPubkey: new PublicKey(""),
wallet,
connection,
network: "mainnet",
jupiterApiKey: "",
maxAllowedAccounts: 64,
priorityFee: 50_000,
});
```
***
## Claim Fees
```typescript theme={null}
const claimTx = await sdk.withdrawVaultFeesTx({
claimer: wallet.publicKey.toBase58(),
vault: "",
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: claimTx,
wallet,
});
```
***
## Check and Trigger Rebalance
```typescript theme={null}
const vault = await sdk.fetchVault("");
const pricedVault = await sdk.loadVaultPrice(vault);
const needsRebalance = await isRebalanceRequired(pricedVault, connection);
if (needsRebalance) {
const tx = await sdk.rebalanceVaultTx({
keeper: wallet.publicKey.toBase58(),
vault_mint: vault.mint.toBase58(),
rebalance_slippage_bps: 100,
per_trade_rebalance_slippage_bps: 100,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
}
```
***
## Set Up Manager Authorities
```typescript theme={null}
const tx = await sdk.editManagersTx(
{
vault: "",
manager: wallet.publicKey.toBase58(),
},
{
managers: [
{
pubkey: "",
fee_split_weight_bps: 6000,
authorities: {
managers: true,
fees: true,
schedule: true,
automation: true,
lp: true,
metadata: true,
deposits: true,
force_rebalance: true,
custom_rebalance: true,
add_token: true,
update_weights: true,
make_direct_swap: true,
},
},
{
pubkey: "",
fee_split_weight_bps: 4000,
authorities: {
managers: false,
fees: false,
schedule: false,
automation: false,
lp: false,
metadata: true,
deposits: true,
force_rebalance: false,
custom_rebalance: false,
add_token: false,
update_weights: true,
make_direct_swap: false,
},
},
],
modification_delay: 172800,
}
);
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet });
```
***
## Enable Automation
```typescript theme={null}
const tx = await sdk.editAutomationTx(
{
vault: "",
manager: wallet.publicKey.toBase58(),
},
{
enabled: true,
rebalance_slippage_threshold_bps: 100,
per_trade_rebalance_slippage_threshold_bps: 100,
rebalance_activation_threshold_abs_bps: 500,
rebalance_activation_threshold_rel_bps: 1000,
rebalance_activation_cooldown: 3600,
modification_delay: 86400,
}
);
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet });
```
***
## Monitor Rebalance Intents for a Vault
```typescript theme={null}
const VAULT_PUBKEY: string = "";
const rebalanceIntents: UIRebalanceIntent[] = await sdk.fetchVaultRebalanceIntents(VAULT_PUBKEY);
for (const ri of rebalanceIntents) {
const data: FormattedRebalanceIntent = ri.formatted_data;
console.log(`Intent: ${data.pubkey}`);
console.log(` Type: ${data.rebalance_type}`);
console.log(` Action: ${data.current_action}`);
console.log(` Owner: ${data.owner}`);
if (ri.deposit_data) {
console.log(` Deposits:`);
for (const token of ri.deposit_data.tokens) {
console.log(` ${token.mint}: ${token.amount}`);
}
}
if (ri.auction_data) {
console.log(` Auction stages:`);
for (const stage of ri.auction_data.auction_stages) {
console.log(` ${new Date(stage.start_time * 1000).toISOString()} - ${new Date(stage.end_time * 1000).toISOString()}`);
}
}
}
```
***
## Add Bounty to a Vault
```typescript theme={null}
const tx = await sdk.addBountyTx({
keeper: wallet.publicKey.toBase58(),
vault: "",
amount: 100_000_000,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
```
***
## Build a Portfolio Tracker
```typescript theme={null}
async function getPortfolio(ownerPubkey: string) {
const allVaults = await sdk.fetchAllVaults();
const portfolio = [];
for (const vault of allVaults) {
const pricedVault = await sdk.loadVaultPrice(vault);
const vaultMint = pricedVault.mint.toBase58();
portfolio.push({
name: pricedVault.formatted!.name,
symbol: pricedVault.formatted!.symbol,
mint: vaultMint,
price: pricedVault.price?.toNumber(),
tvl: pricedVault.tvl?.toNumber(),
composition: pricedVault.formatted!.composition
.filter(a => a.active)
.map(a => ({
mint: a.mint,
weight: a.weight / 100,
amount: a.amount,
})),
});
}
return portfolio;
}
```
***
## Common Patterns
### Error Handling
```typescript theme={null}
try {
const tx = await sdk.buyVaultTx({ /* ... */ });
const sigs = await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
console.log("Success:", sigs);
} catch (error) {
console.error("Transaction failed:", error);
}
```
### Preflight Mode
`simulateTransactions: true` enables a preflight-style send path in the SDK — it does **not** perform offline-only simulation. Transactions are still sent to the network. Do not use on production keys if you expect zero chain side-effects.
```typescript theme={null}
const sigs = await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
simulateTransactions: true,
});
```
### Working with Amounts
Token amounts are always in the smallest unit (lamports):
| Token | Decimals | 1 token = |
| ------- | -------- | -------------------------------- |
| SOL | 9 | 1,000,000,000 lamports |
| USDC | 6 | 1,000,000 |
| General | N | `rawAmount = humanAmount * 10^N` |
This applies to `contributions[].amount`, `withdraw_amount`, `amount_in`, `amount_out`, bounty `amount`, `min_bounty_amount`, and `max_bounty_amount`.
### Vault Mint vs Vault Account
Two addresses are associated with each vault:
* **Vault mint** (`vault.mint`): The token mint of the vault token. Used in `buyVaultTx`, `sellVaultTx`, `rebalanceVaultTx`.
* **Vault account** (`vault.ownAddress` or `vault.formatted.pubkey`): The on-chain state account. Used in `fetchVault`, `addOrEditTokenTx`, `editFeesTx`, etc.
To go from mint → account: `getVaultState(mintPubkey)` (PDA derivation).
To go from account → mint: read `vault.mint` from the fetched vault.
# Keeper Infrastructure
Source: https://docs.symmetry.fi/guides/keeper
Running keeper bots, KeeperMonitor, RebalanceHandler, and earning bounties.
Keepers are off-chain agents that monitor the Symmetry protocol and execute pending tasks in exchange for bounties. All keeper operations are permissionless — every SDK method in the rebalance flow (`updateTokenPricesTx`, `flashSwapTx`, `mintTx`, `redeemTokensTx`, `claimBountyTx`, etc.) can be called by any wallet. The `keeper` parameter is simply the signing wallet.
Developers can build their own keeper bots using the individual SDK methods directly. The SDK also ships with `KeeperMonitor` and `RebalanceHandler` as reference implementations that handle the full lifecycle automatically — use them as-is, or as a starting point for custom keeper logic.
## KeeperMonitor
`KeeperMonitor` is a reference keeper implementation included in the SDK. It continuously polls the protocol and automatically handles:
* Executing configuration intents after activation
* Cancelling expired intents
* Processing rebalance intents (price updates, flash swaps, minting, redeeming, bounty claims)
### Setup
```typescript theme={null}
import { KeeperMonitor } from "@symmetry-hq/sdk";
import { Connection, Keypair } from "@solana/web3.js";
const keypair = Keypair.fromSecretKey(/* your secret key */);
const wallet = {
publicKey: keypair.publicKey,
signTransaction: async (tx) => { tx.sign([keypair]); return tx; },
signAllTransactions: async (txs) => { txs.forEach(tx => tx.sign([keypair])); return txs; },
payer: keypair,
};
const connection = new Connection("https://api.mainnet-beta.solana.com");
const keeper = new KeeperMonitor({
wallet,
connection,
network: "mainnet",
jupiterApiKey: "",
maxAllowedAccounts: 64,
priorityFee: 50_000,
simulateTransactions: false,
});
```
### Running
**Continuous loop:**
```typescript theme={null}
while (true) {
await keeper.update();
await new Promise(resolve => setTimeout(resolve, 10_000));
}
```
**Time-limited run:**
```typescript theme={null}
await keeper.run(600); // run for 10 minutes (minimum 60 seconds)
```
The `run()` method calls `update()` every \~30 seconds, stops after the specified duration, waits 45 seconds for in-flight tasks, then clears internal state.
### How It Works
Each `update()` call:
1. Fetches all vaults and syncs them to an internal map.
2. Fetches all configuration intents. For new intents, starts `monitorIntent()` in the background.
3. Fetches all rebalance intents. For new actionable ones (not `deposit_tokens` or `not_active`), starts `monitorRebalanceIntent()` in the background.
4. Removes closed intents from internal maps.
### Intent Monitoring
For each configuration intent:
1. Wait until `activation_timestamp`.
2. Try to execute via `executeVaultIntentTx` (up to 2 attempts).
3. If execution failed, wait until expiration.
4. After expiration, try to cancel via `cancelVaultIntentTx` (up to 4 attempts).
### Rebalance Intent Monitoring
For each rebalance intent:
1. Skip if `not_active` or `deposit_tokens` (waiting for user action).
2. If `update_prices`: call `updateTokenPricesTx` (up to 5 attempts).
3. During auction windows:
* Compute swap pairs via `getSwapPairs`.
* Fetch Jupiter quotes for profitable pairs (refreshed every 60 seconds).
* Execute `flashSwapTx` for each profitable pair.
* Re-check every \~8 seconds.
4. After auctions end:
* Deposits: call `mintTx` (up to 3 attempts).
* Withdrawals with remaining tokens: call `redeemTokensTx` (up to 3 attempts). **Note:** if the recipient's ATA doesn't exist for a token and the keeper is not the owner, that token is skipped during redemption. Ensure the withdrawer has ATAs for all expected tokens.
* Then: call `claimBountyTx` (up to 3 attempts).
### Requirements
| Requirement | Details |
| --------------- | ------------------------------------------------- |
| SOL balance | Keeper wallet needs SOL for transaction fees |
| Jupiter API key | Required for flash swap quotes on mainnet |
| RPC connection | Reliable RPC with sufficient rate limits |
| Uptime | Keepers should run continuously to not miss tasks |
## RebalanceHandler
`RebalanceHandler` handles a single rebalance intent from start to finish. Use this when you want to process a specific rebalance rather than monitoring all protocol activity.
### One-Shot Run
```typescript theme={null}
import { RebalanceHandler } from "@symmetry-hq/sdk";
import { PublicKey } from "@solana/web3.js";
await RebalanceHandler.run({
intentPubkey: new PublicKey(""),
wallet,
connection,
network: "mainnet",
jupiterApiKey: "",
maxAllowedAccounts: 64,
priorityFee: 50_000,
simulateTransactions: false,
});
```
This fetches the rebalance intent and its vault, creates a handler instance, runs the full lifecycle, and refreshes intent data periodically (every 15 seconds, up to 20 refreshes).
### Manual Control
```typescript theme={null}
const handler = new RebalanceHandler({
intent: uiRebalanceIntent,
vault: vault,
wallet,
connection,
network: "mainnet",
jupiterApiKey: "",
maxAllowedAccounts: 64,
priorityFee: 50_000,
simulateTransactions: false,
});
```
## Bounty System
Keepers earn bounties for completing tasks. Bounties are funded by the user or manager who creates the intent.
### How Bounties Work
1. When a deposit, withdrawal, rebalance, or configuration change is created, a bounty is deposited (typically in WSOL). This includes a **bounty bond** — a fixed amount (set in the protocol's global config as `bounty_bond_amount`) that is locked alongside the bounty.
2. The bounty has a schedule: starts at `min_bounty` and increases to `max_bounty` over time, incentivizing faster execution.
3. Each task completion (price update, flash swap, mint, redeem) is recorded on-chain with the keeper's pubkey and timestamp.
4. After all tasks are done, `claimBountyTx` distributes bounties to all participating keepers proportionally based on the tasks they completed.
5. Unused bounty and the bounty bond are returned to the depositor.
6. The on-chain account rent is also returned.
### Bounty Schedule
```typescript theme={null}
interface FormattedBountySchedule {
min_bounty: number; // raw amount in bounty token's smallest units
max_bounty: number; // raw amount in bounty token's smallest units
min_bounty_until: number;
max_bounty_after: number;
}
```
Between `min_bounty_until` and `max_bounty_after`, the bounty interpolates linearly.
### Bounty Computation
The total WSOL locked when creating a deposit, withdrawal, or rebalance is computed automatically by the SDK. It includes:
| Component | Description |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bounty bond | Fixed lock amount from global config (`bounty_bond_amount`). Returned after completion. |
| Task bounties | `max_bounty_per_task` × number of tasks (auction stages, mint/redeem, claim). |
| Price update bounties | `max_bounty_per_task / bounty_per_price_update_task_divisor` × number of tokens in the vault. More tokens = more price update instructions = higher total. |
Users can override the min/max bounty per task via `min_bounty_amount` and `max_bounty_amount` parameters in `buyVaultTx`, `sellVaultTx`, and `rebalanceVaultTx`. Higher bounties incentivize keepers to process operations faster.
The SDK computes the total automatically — callers do not need to calculate it manually.
### Adding Bounty to a Vault
Vault bounty funds automation (keeper-initiated rebalances):
```typescript theme={null}
const tx: TxPayloadBatchSequence = await sdk.addBountyTx({
keeper: wallet.publicKey.toBase58(),
vault: "",
amount: 100_000_000, // 0.1 SOL (raw: 0.1 * 10^9 = 100_000_000 lamports)
});
```
## Jupiter Integration
The SDK includes utilities for interacting with Jupiter for flash swaps:
```typescript theme={null}
import { getJupTokenLedgerAndSwapInstructions } from "@symmetry-hq/sdk";
const result = await getJupTokenLedgerAndSwapInstructions({
keeper: walletPublicKey,
vaultMintIn: new PublicKey(""), // token deposited to vault
vaultMintOut: new PublicKey(""), // token withdrawn from vault
vaultAmountIn: 1_000_000, // raw amount vault receives
vaultAmountOut: 1_000_000, // raw amount vault gives
swapMode: "ioc",
apiKey: "",
maxJupAccounts: 64,
});
// result contains:
// - tokenLedgerInstruction: TransactionInstruction
// - swapInstruction: TransactionInstruction
// - addressLookupTableAddresses: PublicKey[]
// - quoteResponse: { inAmount, outAmount, ... }
```
### Swap Modes
| Mode | Description |
| ----------- | --------------------------------------------- |
| `exact_in` | Guarantee exact input amount, variable output |
| `exact_out` | Guarantee exact output amount, variable input |
| `ioc` | Immediate-or-cancel — best effort execution |
The Jupiter API call uses reversed mint directions internally. The SDK handles this — you specify `vaultMintIn` (what the vault receives) and `vaultMintOut` (what the vault gives), and the SDK swaps them for the Jupiter query.
# Symmetry
Source: https://docs.symmetry.fi/index
On-chain infrastructure for multi-token vaults on Solana with automated rebalancing, oracle-based pricing, and permissionless execution.
Symmetry is on-chain infrastructure on Solana for creating and managing multi-token vaults. Vaults hold configurable sets of Solana token mints (SPL and Token Extensions/Token22) with target weights, mint their own vault token representing proportional ownership, and use oracle-based pricing for valuation. The protocol is fully permissionless — anyone can create vaults, deposit, withdraw, or run keeper infrastructure.
## What You Can Do
* **Create vaults** — Define a basket of up to 100 token mints (including SPL and Token22) with target weights and oracle configurations.
* **Deposit and withdraw** — Contribute any tokens to a vault and receive vault tokens, or burn vault tokens to receive underlying assets.
* **Automate rebalancing** — Vaults rebalance toward target weights through an auction system powered by off-chain keepers.
* **Configure fees** — Set deposit and withdrawal fees across multiple tiers (management and performance fees are currently disabled).
* **Run keepers** — Operate off-chain bots that process rebalances, execute intents, and earn bounties.
* **Build integrations** — Use the TypeScript SDK to embed vault operations and data queries into any application.
***
Install the SDK, fetch vault data, and submit your first transaction.
Understand vaults, intents, rebalancing, oracles, and the role system.
Complete method-by-method reference for the TypeScript SDK.
## Core Concepts
Multi-token baskets with target weights, oracle pricing, and a vault token mint.
On-chain proposals for vault configuration changes with time-locks and bounties.
Deposits, withdrawals, and periodic rebalances through a multi-step auction flow.
Multi-tier fee structure and multi-source oracle aggregation.
Protocol-wide parameters, feature status, and fee limits.
## Guides
Run keeper bots to process rebalances, execute intents, and earn bounties.
End-to-end code examples for common use cases.
## Quick Reference
| Item | Value |
| ------------------------- | -------------------------------------------------------------- |
| Program ID | `BASKT7aKd8n7ibpUbwLP3Wiyxyi3yoiXsxBk4Hpumate` |
| SDK | `@symmetry-hq/sdk` |
| Chain | Solana |
| Networks | `mainnet`, `devnet` |
| Supported token standards | SPL, Token Extensions (`Token22`) |
| Supported oracle types | Pyth, Raydium CLMM, Raydium CPMM, LST (SPL/Sanctum stake pool) |
| Max tokens per vault | 100 |
| Max managers per vault | 10 |
| License | BUSL-1.1 |
# Quick Start
Source: https://docs.symmetry.fi/quickstart
Install the SDK, connect to Solana, and interact with Symmetry vaults.
## Install
```bash theme={null}
npm install @symmetry-hq/sdk @solana/web3.js @coral-xyz/anchor
```
## Initialize the SDK
```typescript theme={null}
import { Connection } from "@solana/web3.js";
import { SymmetryCore } from "@symmetry-hq/sdk";
const connection = new Connection("https://api.mainnet-beta.solana.com");
const sdk = new SymmetryCore({
connection,
network: "mainnet",
priorityFee: 50_000,
});
```
The SDK supports vault composition and token flows for both SPL and Token Extensions (`Token22`) mints, and supports Pyth, Raydium CLMM, Raydium CPMM, and LST (SPL/Sanctum stake pool) oracle configurations.
## Fetch Vaults
```typescript theme={null}
const vaults = await sdk.fetchAllVaults();
console.log(`Found ${vaults.length} vaults`);
const vault = await sdk.fetchVault("");
console.log(vault.formatted);
```
## Load Prices
Fetching a vault does not include live prices. Call `loadVaultPrice` to get current oracle prices and compute TVL:
```typescript theme={null}
let vault = await sdk.fetchVault("");
vault = await sdk.loadVaultPrice(vault);
console.log("TVL:", vault.tvl?.toString());
console.log("Token Price:", vault.price?.toString());
for (const asset of vault.formatted!.composition) {
if (!asset.active) continue;
console.log(`${asset.mint}: weight=${asset.weight / 100}%`);
}
```
## Set Up a Wallet
For Node.js environments, create a wallet adapter from a keypair:
```typescript theme={null}
import { Keypair } from "@solana/web3.js";
const keypair = Keypair.fromSecretKey(/* your secret key bytes */);
const wallet = {
publicKey: keypair.publicKey,
signTransaction: async (tx: T): Promise => {
(tx as any).sign([keypair]);
return tx;
},
signAllTransactions: async (txs: T[]): Promise => {
txs.forEach((tx: any) => tx.sign([keypair]));
return txs;
},
payer: keypair,
};
```
Never commit private keys to version control. Use environment variables or a secure key management solution.
## Create a Vault
```typescript theme={null}
const result = await sdk.createVaultTx({
creator: wallet.publicKey.toBase58(),
start_price: "1.0",
name: "My Vault",
symbol: "MV",
metadata_uri: "https://arweave.net/your-metadata-json", // URL to JSON with name, symbol, description, image, cover
});
console.log("Vault mint:", result.mint);
console.log("Vault account:", result.vault);
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: result,
wallet,
});
```
The `metadata_uri` should point to a JSON file containing `name`, `symbol`, `description`, `image`, and `cover` fields. See [Vaults](/concepts/vaults#metadata-uri) for the full metadata spec.
After creation, the vault has no tokens. Add tokens with `addOrEditTokenTx` and set weights with `updateWeightsTx`. For Pyth oracle price feed IDs, see [Pyth Price Feed IDs](https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids). See [Vaults](/concepts/vaults) for the full workflow.
## Deposit into a Vault
```typescript theme={null}
const tx = await sdk.buyVaultTx({
buyer: wallet.publicKey.toBase58(),
vault_mint: "",
contributions: [
{ mint: "So11111111111111111111111111111111111111112", amount: 1_000_000_000 },
],
rebalance_slippage_bps: 100,
per_trade_rebalance_slippage_bps: 100,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
const lockTx = await sdk.lockDepositsTx({
buyer: wallet.publicKey.toBase58(),
vault_mint: "",
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: lockTx,
wallet,
});
```
After locking, the rebalance is processed: price updates, auctions, and minting. Any wallet may execute these stage methods directly; keeper is an actor role, not a privilege role. See [Rebalancing](/concepts/rebalancing) for the full flow.
## Common Use Cases
| Use Case | How |
| -------------------------- | ----------------------------------------------------------------------------------------- |
| Create a multi-token vault | `createVaultTx` → `addOrEditTokenTx` → `updateWeightsTx` (supports SPL and Token22 mints) |
| Deposit into a vault | `buyVaultTx` → `lockDepositsTx` → keeper processes → vault tokens minted |
| Withdraw from a vault | `sellVaultTx` → keeper processes → underlying tokens redeemed |
| Fast withdrawal | `sellVaultTx` with all mints in `keep_tokens` → `redeemTokensTx` |
| Read vault data | `fetchVault` → `loadVaultPrice` → read `vault.formatted` |
| Run a keeper bot | Create `KeeperMonitor` and call `update()` in a loop |
| Claim fees | `withdrawVaultFeesTx` |
## Next Steps
* [Protocol Overview](/concepts/overview) — Architecture, roles, and core concepts
* [Vaults](/concepts/vaults) — Vault structure, creation, and configuration
* [Integration Examples](/guides/examples) — End-to-end code for common use cases
* [SDK Reference](/sdk/reference) — Complete API documentation
# SDK Reference
Source: https://docs.symmetry.fi/sdk/reference
Complete method-by-method reference for the @symmetry-hq/sdk package.
## Installation
```bash theme={null}
npm install @symmetry-hq/sdk @solana/web3.js @coral-xyz/anchor
```
## Imports
```typescript theme={null}
import {
SymmetryCore,
KeeperMonitor,
RebalanceHandler,
// Transaction types
VaultCreationTx,
TxPayloadBatchSequence,
VersionedTxs,
// Core data types
GlobalConfig,
Vault,
VaultFilter,
Intent,
IntentFilter,
RebalanceIntent,
RebalanceIntentFilter,
// Formatted types
FormattedGlobalConfig,
FormattedVault,
FormattedAsset,
FormattedIntent,
FormattedRebalanceIntent,
UIRebalanceIntent,
// Settings input types
EditCreatorSettings,
EditManagerSettings,
EditFeeSettings,
EditScheduleSettings,
EditAutomationSettings,
EditLpSettings,
EditMetadataSettings,
EditDepositsSettings,
EditForceRebalanceSettings,
EditCustomRebalanceSettings,
EditAddTokenSettings,
EditUpdateWeightsSettings,
EditMakeDirectSwapSettings,
AddOrEditTokenInput,
OracleInput,
UpdateWeightsInput,
MakeDirectSwapInput,
Settings,
TaskContext,
TaskType,
// Rebalance data types
DepositData,
PriceUpdatesData,
AuctionData,
MintData,
RedeemData,
ClaimBountyData,
// Utilities
getJupTokenLedgerAndSwapInstructions,
getSwapPairs,
isRebalanceRequired,
} from "@symmetry-hq/sdk";
```
## Token and Oracle Support
* Token standards: SPL and Token Extensions (`Token22`) are supported in vault composition and token flows (`addOrEditTokenTx`, `buyVaultTx`, `sellVaultTx`, `depositTokensTx`, `redeemTokensTx`, etc.).
* Oracle types: Pyth, Raydium CLMM, Raydium CPMM, and LST (SPL/Sanctum stake pool) are supported.
## SymmetryCore
### Constructor
```typescript theme={null}
const sdk = new SymmetryCore({
connection: Connection,
network: "devnet" | "mainnet",
priorityFee?: number, // micro-lamports, default: 25,000
});
```
### Configuration
#### `setPriorityFee(priorityFee: number): void`
Updates the priority fee for all subsequent transactions.
#### `fetchGlobalConfig(): Promise`
Returns the protocol-wide configuration account.
***
## Vault Methods
### Fetching
#### `fetchVault(vaultPubkey: string): Promise`
Fetch a single vault by its on-chain account public key. Does not load oracle prices — call `loadVaultPrice()` separately.
#### `fetchMultipleVaults(vaultPubkeys: string[]): Promise