@symmetry-hq/sdk
v1.0.18
Published
Symmetry V3 SDK
Downloads
1,787
Readme
@symmetry-hq/sdk
TypeScript SDK for the Symmetry V3 protocol on Solana — on-chain infrastructure for creating and managing multi-token vaults with automated rebalancing, oracle-based pricing, and permissionless keeper execution.
Documentation | LLMs.txt | App | Discord
Have questions or need help integrating? Join our Discord.
Installation
npm install @symmetry-hq/sdkPeer Dependencies
npm install @solana/web3.js @coral-xyz/anchorQuick Start
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, // optional, micro-lamports (default: 25,000)
});
// Fetch all vaults
const vaults = await sdk.fetchAllVaults();
// Fetch a single vault and load its price
let vault = await sdk.fetchVault("<VAULT_PUBKEY>");
vault = await sdk.loadVaultPrice(vault);
console.log(vault.formatted);Core Concepts
Vaults
A vault is an on-chain account that holds a configurable set of SPL tokens with target weights. Each vault:
- Holds multiple SPL tokens with individually configurable target weights
- Mints its own SPL token — vault token holders have proportional ownership of the underlying tokens
- Supports multi-source oracle pricing (Pyth, Raydium CPMM; Raydium CLMM and Switchboard are currently disabled) with configurable aggregation
- Has a flexible fee structure (deposit, withdrawal, management, performance) split across creator, host platform, managers, and protocol. Management and performance fees are currently disabled at the protocol level.
- Supports up to 100 tokens, 10 managers, and 4 oracle sources per token
Intents
All configuration changes to a vault go through an intent system. An intent is an on-chain change request — editing fees, metadata, token composition, weights, etc. — with optional time-locks and bounty incentives for keepers to execute them.
Rebalance Intents
Deposits, withdrawals, and periodic rebalances are processed through the same on-chain flow. They progress through a multi-step auction:
- Deposit tokens — user contributes tokens to the vault
- Lock deposits — freezes contributions and starts the process
- Price updates — oracle prices are refreshed on-chain
- Auction — keepers execute flash swaps to settle the vault toward target weights
- Mint / Redeem — vault tokens are minted (deposits) or underlying tokens are released (withdrawals)
- Claim bounty — keepers collect their rewards
Keepers
Keepers are off-chain agents that monitor the protocol, execute pending tasks (intents, price updates, auctions), and earn bounties. All keeper operations are permissionless — developers can build custom keeper bots using the SDK's transaction-building methods. The SDK also ships with KeeperMonitor and RebalanceHandler as reference implementations that work out of the box.
API Reference
Constructor
const sdk = new SymmetryCore({
connection: Connection, // Solana RPC connection
network: "devnet" | "mainnet",
priorityFee?: number, // compute unit price in micro-lamports (default: 25,000)
});Configuration
setPriorityFee(priorityFee: number): void
Updates the priority fee for all subsequent transactions.
fetchGlobalConfig(): Promise<GlobalConfig>
Returns the protocol-wide configuration account (fee limits, bounty settings, rebalance parameters).
Fetching Vaults
fetchVault(vaultPubkey: string): Promise<Vault>
Fetch a single vault by its account public key.
fetchMultipleVaults(vaultPubkeys: string[]): Promise<Map<string, Vault>>
Batch-fetch multiple vaults. Returns a map keyed by public key.
fetchAllVaults(filter?: VaultFilter): Promise<Vault[]>
Fetch all vaults on-chain with an optional role filter:
type VaultFilter = {
type: "creator" | "host" | "manager";
pubkey: string;
};fetchCreatedVaults(creatorPubkey: string): Promise<Vault[]>
Fetch vaults created by a specific public key.
fetchHostedVaults(hostPubkey: string): Promise<Vault[]>
Fetch vaults hosted by a specific public key.
fetchManagedVaults(managerPubkey: string): Promise<Vault[]>
Fetch vaults managed by a specific public key.
fetchVaultsFromMints(mints: string[]): Promise<Map<string, Vault>>
Look up vaults by their SPL token mint addresses. Returns a map keyed by mint.
deriveVaultsByMints(mints: string[]): Promise<Map<string, string>>
Derive vault account addresses from mint addresses without RPC calls. Returns a mint-to-pubkey map.
loadVaultPrice(vault: Vault): Promise<Vault>
Loads live oracle prices for all tokens in the vault, computes the vault's total value, and returns the updated object.
Fetching Intents
fetchIntent(intentPubkey: string): Promise<Intent>
Fetch a single intent by its public key.
fetchMultipleIntents(intentPubkeys: string[]): Promise<Map<string, Intent>>
Batch-fetch multiple intents.
fetchAllIntents(filter?: IntentFilter): Promise<Intent[]>
Fetch all intents with an optional filter:
type IntentFilter = {
type: "manager" | "vault";
pubkey: string;
};fetchCreatedIntents(creatorPubkey: string): Promise<Intent[]>
Fetch intents created by a specific manager.
fetchVaultIntents(vaultPubkey: string): Promise<Intent[]>
Fetch all intents for a specific vault.
Fetching Rebalance Intents
fetchRebalanceIntent(pubkey: string): Promise<UIRebalanceIntent>
Fetch a single rebalance intent.
fetchMultipleRebalanceIntents(pubkeys: string[]): Promise<Map<string, UIRebalanceIntent>>
Batch-fetch multiple rebalance intents.
fetchAllRebalanceIntents(filter?: RebalanceIntentFilter): Promise<UIRebalanceIntent[]>
Fetch all rebalance intents with an optional filter:
type RebalanceIntentFilter = {
type: "owner" | "vault";
pubkey: string;
};fetchOwnerRebalanceIntents(ownerPubkey: string): Promise<UIRebalanceIntent[]>
Fetch rebalance intents owned by a specific public key.
fetchVaultRebalanceIntents(vaultPubkey: string): Promise<UIRebalanceIntent[]>
Fetch rebalance intents for a specific vault.
Fetching Fee Accounts
fetchWithdrawVaultFees(pubkey: string): Promise<WithdrawVaultFees>
Fetch a single fee withdrawal account.
fetchMultipleWithdrawVaultFees(pubkeys: string[]): Promise<Map<string, WithdrawVaultFees>>
Batch-fetch multiple fee withdrawal accounts.
fetchAllWithdrawVaultFees(filter?: WithdrawVaultFeesFilter): Promise<WithdrawVaultFees[]>
Fetch all fee withdrawal accounts with an optional filter:
type WithdrawVaultFeesFilter = {
type: "vault" | "manager" | "creator" | "host" | "symmetry";
pubkey: string;
};Convenience methods: fetchVaultWithdrawVaultFees, fetchManagerWithdrawVaultFees, fetchCreatorWithdrawVaultFees, fetchHostWithdrawVaultFees, fetchSymmetryWithdrawVaultFees.
Creating Vaults
createVaultTx(params): Promise<VaultCreationTx>
Creates a new vault. Returns the vault's token mint, account address, and the transaction payload.
The metadata_uri is a URL (max 200 chars) pointing to a JSON file describing the vault. It should contain name (vault token name), symbol (vault token ticker), description (vault description), image (token image/icon URL), and cover (vault cover image URL). You can add any extra fields for your own integrations (social links, website, etc.):
{
"name": "My Vault",
"symbol": "MVT",
"description": "A multi-token vault.",
"image": "https://arweave.net/token-image-url",
"cover": "https://arweave.net/cover-image-url"
}const result = await sdk.createVaultTx({
creator: wallet.publicKey.toBase58(),
start_price: "1.0", // initial price in USDC
name: "My Vault",
symbol: "MVT",
metadata_uri: "https://arweave.net/your-metadata-json", // URL to JSON with name, symbol, description, image, cover
host_platform_params: { // optional
host_pubkey: "<HOST_PUBKEY>",
host_deposit_fee_bps: 10, // 0.1%
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,
});Editing Vault Settings
All edit methods create an intent that modifies the vault. If the setting has no modification delay and no scheduled activation, the intent is executed immediately in the same transaction.
Each method accepts a TaskContext and a settings object, returning a TxPayloadBatchSequence:
interface TaskContext {
vault: string; // vault public key
manager: string; // authorized manager public key
activation_timestamp?: number; // optional scheduled activation (unix seconds)
expiration_timestamp?: number; // optional expiration
min_bounty?: number; // optional min bounty override
max_bounty?: number; // optional max bounty override
}| Method | Settings Type | Description |
|--------|--------------|-------------|
| editCreatorTx | EditCreatorSettings | Transfer vault creator role |
| editManagersTx | EditManagerSettings | Edit managers, weights, and authority bitmasks |
| editFeesTx | EditFeeSettings | Update fee structure |
| editScheduleTx | EditScheduleSettings | Configure repeating time cycles that gate deposits, automation, and management windows |
| editAutomationTx | EditAutomationSettings | Configure rebalance automation |
| editLpTx | EditLpSettings | Configure LP settings |
| editMetadataTx | EditMetadataSettings | Update name, symbol, and metadata URI (JSON with name, symbol, description, image, cover) |
| editDepositsTx | EditDepositsSettings | Enable or disable deposits |
| editForceRebalanceTx | EditForceRebalanceSettings | Enable or disable force rebalance |
| editCustomRebalanceTx | EditCustomRebalanceSettings | Enable or disable custom rebalance |
| editAddTokenDelayTx | EditAddTokenSettings | Set time-lock for adding tokens |
| editUpdateWeightsDelayTx | EditUpdateWeightsSettings | Set time-lock for weight updates |
| editSwapDelayTx | EditMakeDirectSwapSettings | Set time-lock for direct swaps |
Token Composition
addOrEditTokenTx(context: TaskContext, settings: AddOrEditTokenInput): Promise<TxPayloadBatchSequence>
Add a new token to the vault or update an existing token's oracle configuration:
const tx = await sdk.addOrEditTokenTx(
{ vault: "<VAULT_PUBKEY>", manager: "<MANAGER>" },
{
token_mint: "<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: "<PYTH_PRICE_ACCOUNT>",
weight_bps: 10000,
is_required: true,
conf_thresh_bps: 200,
volatility_thresh_bps: 200,
max_slippage_bps: 1000,
min_liquidity: 0,
staleness_thresh: 120,
staleness_conf_rate_bps: 50,
token_decimals: 9,
twap_seconds_ago: 0,
twap_secondary_seconds_ago: 0,
quote_token: "usd",
},
],
}
);updateWeightsTx(context: TaskContext, settings: UpdateWeightsInput): Promise<TxPayloadBatchSequence>
Update target weights for all tokens in the vault:
const tx = await sdk.updateWeightsTx(
{ vault: "<VAULT_PUBKEY>", manager: "<MANAGER>" },
{
token_weights: [
{ mint: "<TOKEN_MINT_1>", weight_bps: 5000 },
{ mint: "<TOKEN_MINT_2>", weight_bps: 3000 },
{ mint: "<TOKEN_MINT_3>", weight_bps: 2000 },
], // basis points, must sum to 10000
}
);makeDirectSwapTx(context, settings, jup_swap_ix?): Promise<TxPayloadBatchSequence>
Execute a direct token swap within the vault via Jupiter.
Intent Execution & Cancellation
executeVaultIntentTx(params): Promise<TxPayloadBatchSequence>
Execute a pending intent after its time-lock has elapsed:
await sdk.executeVaultIntentTx({
keeper: wallet.publicKey.toBase58(),
intent: "<INTENT_PUBKEY>",
});executeDirectSwapVaultIntentTx(params): Promise<TxPayloadBatchSequence>
Execute a pending direct swap intent with a flash swap:
await sdk.executeDirectSwapVaultIntentTx({
keeper: wallet.publicKey.toBase58(),
intent: "<INTENT_PUBKEY>",
jup_swap_ix: jupiterSwapInstruction,
jup_address_lookup_table_addresses: [...],
});cancelVaultIntentTx(params): Promise<TxPayloadBatchSequence>
Cancel and close an intent account:
await sdk.cancelVaultIntentTx({
keeper: wallet.publicKey.toBase58(),
intent: "<INTENT_PUBKEY>",
});Deposits & Withdrawals
buyVaultTx(params): Promise<TxPayloadBatchSequence>
Deposit tokens into a vault to receive vault tokens:
const tx = await sdk.buyVaultTx({
buyer: wallet.publicKey.toBase58(),
vault_mint: "<VAULT_MINT>",
contributions: [
{ mint: "So11111111111111111111111111111111111111112", amount: 1_000_000_000 }, // 1 SOL
{ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: 100_000_000 }, // 100 USDC
],
rebalance_slippage_bps: 100, // optional (default: 1%)
per_trade_rebalance_slippage_bps: 100, // optional
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});depositTokensTx(params): Promise<TxPayloadBatchSequence>
Add more tokens to an existing deposit before locking:
const tx = await sdk.depositTokensTx({
buyer: wallet.publicKey.toBase58(),
contributions: [{ mint: "<MINT>", amount: 500_000_000 }],
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
});lockDepositsTx(params): Promise<TxPayloadBatchSequence>
Lock deposits and start the auction process:
const tx = await sdk.lockDepositsTx({
buyer: wallet.publicKey.toBase58(),
vault_mint: "<VAULT_MINT>",
});sellVaultTx(params): Promise<TxPayloadBatchSequence>
Withdraw from a vault by burning vault tokens:
const tx = await sdk.sellVaultTx({
seller: wallet.publicKey.toBase58(),
vault_mint: "<VAULT_MINT>",
withdraw_amount: 1_000_000, // vault tokens to burn
keep_tokens: [], // mints to receive directly without rebalancing
rebalance_slippage_bps: 100,
});Rebalancing
rebalanceVaultTx(params): Promise<TxPayloadBatchSequence>
Trigger a rebalance to bring the vault back to its target weights:
const tx = await sdk.rebalanceVaultTx({
keeper: wallet.publicKey.toBase58(),
vault_mint: "<VAULT_MINT>",
rebalance_slippage_bps: 100,
});cancelRebalanceIntentTx(params): Promise<TxPayloadBatchSequence>
Cancel an active rebalance intent:
const tx = await sdk.cancelRebalanceIntentTx({
keeper: wallet.publicKey.toBase58(),
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
});Oracle Price Updates
updateTokenPricesTx(params): Promise<TxPayloadBatchSequence>
Refresh on-chain oracle prices for all tokens in a vault during a rebalance:
const tx = await sdk.updateTokenPricesTx({
keeper: wallet.publicKey.toBase58(),
vault: "<VAULT_PUBKEY>",
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
});updatePythPriceFeedsTx(params): Promise<TxPayloadBatchSequence>
Update specific Pyth price feeds from on-chain price accounts:
const tx = await sdk.updatePythPriceFeedsTx({
keeper: wallet.publicKey.toBase58(),
accounts: ["<PYTH_PRICE_ACCOUNT_1>", "<PYTH_PRICE_ACCOUNT_2>"],
});Flash Swaps
flashSwapTx(params): Promise<TxPayloadBatchSequence>
Execute an atomic flash swap during rebalance auctions — withdraws tokens from the vault, performs a swap (e.g. via Jupiter), and deposits the result back in a single transaction:
const tx = await sdk.flashSwapTx({
keeper: wallet.publicKey.toBase58(),
vault: "<VAULT_PUBKEY>",
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
mint_in: "<DEPOSIT_TOKEN_MINT>",
mint_out: "<WITHDRAW_TOKEN_MINT>",
amount_in: 1_000_000,
amount_out: 1_000_000,
jup_swap_ix: jupiterSwapInstruction,
jup_token_ledger_ix: jupiterTokenLedgerInstruction,
jup_address_lookup_table_addresses: [...],
});Minting & Redeeming
mintTx(params): Promise<TxPayloadBatchSequence>
Mint vault tokens after a deposit completes its auctions:
const tx = await sdk.mintTx({
keeper: wallet.publicKey.toBase58(),
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
});redeemTokensTx(params): Promise<TxPayloadBatchSequence>
Redeem underlying tokens from a completed withdrawal:
const tx = await sdk.redeemTokensTx({
keeper: wallet.publicKey.toBase58(),
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
});Bounties
addBountyTx(params): Promise<TxPayloadBatchSequence>
Add bounty to a vault for incentivizing keeper automation:
const tx = await sdk.addBountyTx({
keeper: wallet.publicKey.toBase58(),
vault: "<VAULT_PUBKEY>",
amount: 100_000_000, // lamports
});claimBountyTx(params): Promise<TxPayloadBatchSequence>
Claim bounty rewards after completing rebalance tasks:
const tx = await sdk.claimBountyTx({
keeper: wallet.publicKey.toBase58(),
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
});Fee Management
withdrawVaultFeesTx(params): Promise<TxPayloadBatchSequence>
Withdraw and claim accumulated fees from a vault. Automatically detects which fee types the claimer is authorized to collect (protocol, creator, host, or manager fees):
const tx = await sdk.withdrawVaultFeesTx({
claimer: wallet.publicKey.toBase58(),
vault: "<VAULT_PUBKEY>",
});claimTokenFeesFromVaultTx(params): Promise<TxPayloadBatchSequence>
Claim remaining fee tokens from an existing WithdrawVaultFees account:
const tx = await sdk.claimTokenFeesFromVaultTx({
claimer: wallet.publicKey.toBase58(),
withdrawVaultFees: "<WITHDRAW_VAULT_FEES_PUBKEY>",
});Lookup Tables
rewriteLookupTablesTx(params): Promise<TxPayloadBatchSequence>
Rebuild address lookup tables for a vault (needed when LUTs are full after adding tokens):
const tx = await sdk.rewriteLookupTablesTx({
signer: wallet.publicKey.toBase58(),
vault_mint: "<VAULT_MINT>",
additional_accounts: ["<ACCOUNT_1>", "<ACCOUNT_2>"],
});Sending Transactions
All transaction-building methods return a TxPayloadBatchSequence. Batches are sent sequentially; transactions within a batch are sent in parallel.
signAndSendTxPayloadBatchSequence(params): Promise<TransactionSignature[][]>
const signatures = await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
simulateTransactions: false, // optional
});signAndSendVersionedTxs(params): Promise<TransactionSignature[][]>
const signatures = await sdk.signAndSendVersionedTxs({
versionedTxs,
wallet,
});Keeper Monitor (Reference Implementation)
KeeperMonitor is an example keeper implementation included in the SDK. It provides automated monitoring and execution of all pending protocol tasks.
import { KeeperMonitor } from "@symmetry-hq/sdk";
const keeper = new KeeperMonitor({
wallet, // wallet with signing capability
connection, // Solana RPC connection
network: "mainnet",
jupiterApiKey: "<JUP_API_KEY>",
maxAllowedAccounts: 64,
priorityFee: 50_000, // optional
simulateTransactions: false, // optional
});
while (true) {
await keeper.update();
await new Promise(r => setTimeout(r, 10_000));
}The keeper automatically monitors and executes pending intents, handles the full rebalance lifecycle (price updates, Jupiter flash swaps, minting, redeeming, bounty claims), and manages concurrent task execution.
Standalone Utilities
| Export | Description |
|--------|-------------|
| getJupTokenLedgerAndSwapInstructions | Build Jupiter swap instructions for flash swaps |
| getSwapPairs | Compute required swap pairs for a rebalance intent |
| isRebalanceRequired | Check whether a vault needs rebalancing based on its threshold settings |
Constants
| Constant | Value | Description |
|----------|-------|-------------|
| VAULTS_V3_PROGRAM_ID | BASKT7aKd8n7ibpUbwLP3Wiyxyi3yoiXsxBk4Hpumate | Program address |
| COMPUTE_UNITS | 1,000,000 | Default compute unit limit |
| PRIORITY_FEE | 25,000 | Default priority fee (micro-lamports) |
| MAX_SUPPORTED_TOKENS_PER_VAULT | 100 | Max tokens per vault |
| MAX_MANAGERS_PER_VAULT | 10 | Max managers per vault |
| MAX_ORACLES_PER_TOKEN | 4 | Max oracle sources per token |
| HUNDRED_PERCENT_BPS | 10,000 | 100% in basis points |
Supported Oracles
| Type | Enum | Description |
|------|------|-------------|
| Pyth | 0 | Pyth Network price feeds via Hermes (Price Feed IDs) |
| Raydium CLMM | 1 | Raydium Concentrated Liquidity AMM TWAP (currently disabled on-chain) |
| Raydium CPMM | 2 | Raydium Constant Product AMM TWAP |
| Switchboard | 3 | Switchboard oracle feeds (currently disabled on-chain) |
Each token in a vault supports up to 4 oracle sources with configurable aggregation (e.g. weighted percentile).
Networks
| Network | WSOL Mint | USDC Mint |
|---------|-----------|-----------|
| mainnet | So11111111111111111111111111111111111111112 | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
| devnet | So11111111111111111111111111111111111111112 | USDCoctVLVnvTXBEuP9s8hntucdJokbo17RwHuNXemT |
License
BUSL-1.1 (Business Source License 1.1)
