Setup
All examples use this shared setup:Copy
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 <T>(tx: T): Promise<T> => {
(tx as any).sign([keypair]);
return tx;
},
signAllTransactions: async <T>(txs: T[]): Promise<T[]> => {
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,
});
Create a Vault with Tokens
Creates a 2-token vault with SOL (50%) and USDC (50%). Themetadata_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:
Copy
{
"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"
}
Copy
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
Copy
const VAULT_MINT = "<VAULT_TOKEN_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
Copy
const sellTx: TxPayloadBatchSequence = await sdk.sellVaultTx({
seller: wallet.publicKey.toBase58(),
vault_mint: "<VAULT_TOKEN_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
Copy
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 fromvault.composition to skip auctions:
Copy
const vault = await sdk.fetchVault("<VAULT_PUBKEY>");
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: "<REBALANCE_INTENT_PUBKEY>",
});
await sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: redeemTx, wallet });
Read Vault Data
Copy
let vault = await sdk.fetchVault("<VAULT_PUBKEY>");
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
Copy
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
Copy
const tx = await sdk.editFeesTx(
{
vault: "<VAULT_PUBKEY>",
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
Copy
const keeper = new KeeperMonitor({
wallet,
connection,
network: "mainnet",
jupiterApiKey: "<YOUR_JUPITER_API_KEY>",
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
Copy
await RebalanceHandler.run({
intentPubkey: new PublicKey("<REBALANCE_INTENT_PUBKEY>"),
wallet,
connection,
network: "mainnet",
jupiterApiKey: "<JUP_API_KEY>",
maxAllowedAccounts: 64,
priorityFee: 50_000,
});
Claim Fees
Copy
const claimTx = await sdk.withdrawVaultFeesTx({
claimer: wallet.publicKey.toBase58(),
vault: "<VAULT_PUBKEY>",
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: claimTx,
wallet,
});
Check and Trigger Rebalance
Copy
const vault = await sdk.fetchVault("<VAULT_PUBKEY>");
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
Copy
const tx = await sdk.editManagersTx(
{
vault: "<VAULT_PUBKEY>",
manager: wallet.publicKey.toBase58(),
},
{
managers: [
{
pubkey: "<MANAGER_1_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: "<MANAGER_2_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
Copy
const tx = await sdk.editAutomationTx(
{
vault: "<VAULT_PUBKEY>",
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
Copy
const VAULT_PUBKEY: string = "<VAULT_PUBKEY>";
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
Copy
const tx = await sdk.addBountyTx({
keeper: wallet.publicKey.toBase58(),
vault: "<VAULT_PUBKEY>",
amount: 100_000_000,
});
await sdk.signAndSendTxPayloadBatchSequence({
txPayloadBatchSequence: tx,
wallet,
});
Build a Portfolio Tracker
Copy
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
Copy
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.Copy
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 |
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 SPL token mint of the vault token. Used inbuyVaultTx,sellVaultTx,rebalanceVaultTx. - Vault account (
vault.ownAddressorvault.formatted.pubkey): The on-chain state account. Used infetchVault,addOrEditTokenTx,editFeesTx, etc.
getVaultState(mintPubkey) (PDA derivation).
To go from account → mint: read vault.mint from the fetched vault.