Symmetry SDK is utilized in Symmetry UI (app.symmetry.fi), which is open-source. You can fork the repository and deploy it as your own asset management platform, or modify & add new features.
Software Development Kit for interacting with Symmetry Baskets Program
To install the sdk, run:
npm install @symmetry-hq/baskets-sdk
Initialize SDK
import { BasketsSDK } from"@symmetry-hq/baskets-sdk";let basketsSdk:BasketsSDK=awaitBasketsSDK.init(// rpc connection connection: Connection,// wallet (optional, can be provided later, using setWallet() wallet: Wallet,);// load list of supported tokens(returns TokenSettings array)let tokens:TokenSettings[] =basketsSdk.getTokenListData();// get tokenId(tokenId in symmetry supported tokens) from mintlet tokenId =basketsSdk.tokenIdFromMint("So11111111111111111111111111111111111111112");// set walletbasketsSdk.setWallet(wallet: Wallet);
Create/Edit/Close Basket
Create Basket with specific settings and rules
Checkout Types and Full Example for more information on how settings should be provided.
let basket:Basket=awaitbasketsSdk.createBasket(params: CreateBasketParams);
Edit Basket (Only for Mutable & Permissioned baskets)
// Option 1 - (Simple) Directly set desired compositionawait basketsSdk.simpleEditBasket(basket: Basket, params: SimpleEditParams);// Option 2 - (Advanced) Provide full settings with rulesawait basketsSdk.editBasket(basket: Basket, params: CreateBasketParams);
Close Basket (Basket TVL == 0 & no active BuyStates is required)
let basket:Basket=awaitbasketsSdk.loadFromPubkey(pubkey: PublicKey);
Load list of baskets based on filters (manager/host platform public key)
let baskets:Basket[] =awaitbasketsSdk.findBaskets(filters: FilterOption[]);let baskets:Basket[] =awaitbasketsSdk.findBaskets([{ filterType:"manager"|"host", filterPubkey: PublicKey}]);
You can also load baskets' current compositions & parsed data such as basket name, symbol, current TVL - basketWorth, current price - rawPrice, etc...
let currentCompositions:CurrentComposition[] =awaitbasketsSdk.getCurrentCompositions(baskets: Basket[]);
Buy Basket Tokens
There are three ways to buy a basket token.
Option 1 (Simple):
Buy basket with a single token (token should be in the basket composition)
// Fetch expected amount of Basket tokens after contributionlet expectedAmount =awaitbasketsSdk.computeMintAmountWithSingleToken( basket: Basket, token: PublicKey, amount: number);// Contribute single token and mint Basket tokenslet tx:TransactionSignature=awaitbasketsSdk.buyWithSingleToken( basket: Basket, token: PublicKey, amount: number);
Option 2 (Simple): Contribute all tokens from baskets composition
Works for baskets with no more than 5 tokens
Amounts for tokens should be provided in the same order tokens are present in baskets composition.
Baskets composition can be found in Basket object of a basket, or using getCurrentCompositions()
// Fetch expected amount of Basket tokens after contributionlet expectedAmount:number=awaitbasketsSdk.computeMintAmount( basket: Basket, amounts: number[]);// Contribute tokens and mint Basket tokenslet tx:TransactionSignature=awaitbasketsSdk.buyBasketInstant( basket: Basket, amounts: number[]);
Option 3 (Advanced):
Users also have option to contribute USDC to the basket.
Symmetry Engine will use that USDC to buy underlying tokens
(rebalance usdc to baskets current composition based on their target weights)
After rebalancing, basket tokens will be minted for user.
Step 1: Contribute USDC to Basket and create BuyState (Temporary on-chain account)
Initially BuyState will have the information about users contribution.
let buyState:BuyState=awaitbasketsSdk.buyBasket( basket: Basket, amountUsdc: number);
Step 2: Rebalance BuyState: Buy underlying assets in the basket using contributed USDC.
During rebalancing process, BuyState will store the information about already purchased
assets, as well as remaining amount of USDC.
Users can always force-rebalance their BuyState, but it is also done by
Symmetry engine (BuyState will automatically rebalance within 3 minutes)
let txs:TransactionSignature[] =await basketsSdk.rebalanceBuyState(buyState: BuyState);
Step 3: Mint Basket Tokens & Close Buy State.
During this stage, all the assets acquired during rebalancing process will be added
to the basket. Corresponding basket tokens will be minted for user, and circulating supply
of the basket tokens will increase by same amount. After, it will safely close BuyState.
Users can call force-mint function after 3 minutes, but Symmetry engine will also
automatically handle this step. Rent for creating temporary account goes back to user.
let tx:TransactionSignature=await basketsSdk.mintBasket(buyState: BuyState);
Helpers:
// Find active BuyStates for user that have been created after Step 1let buyStates:BuyState[] =await basketsSdk.findActiveBuyStates(user: PublicKey);// Fetch specific BuyState using its PublicKeylet buyState:BuyState[] =await basketsSdk.fetchBuyStateFromPubkey(pubkey: PublicKey);// Claim Tokens from halted BuyState// In a case where BuyState has been created more than 30 minutes ago,// and mint was unsuccessful for some reason// (no liquidity for token / oracle feed not live / chain congestion)// contributed token(s) can be claimed back by user.let tx =awaitbasketsSdk.claimTokensFromBuyState(pubkey: PublicKey)
Sell Basket
There are two ways to sell basket tokens.
Option 1 (Simple): Sell to a specific token (token should be in baskets composition)
Users can directly claim their portion of the tokens from Baskets composition.
They also have an option to rebalance those tokens to USDC before claiming.
Step 1: Burn Basket Tokens and create SellState (Temporary on-chain account)
SellState is an on-chain account which has exactly same structure as Basket.
Initially SellState will store baskets composition with amounts corresponding to users holdings.
Circulating supply will decrease by burn amount and users Basket tokens will be burned.
From here, this SellState is already independent from the original Basket.
User can specify if they want to rebalance their to-be-claimed tokens to USDC
let sellState:Basket=await basketsSdk.sellBasket(basket: Basket, amount: number, rebalance: boolean);
Step 2: Rebalance SellState(Basket): Rebalance underlying assets to USDC
This step is optional, user can skip this step and directly claim underlying tokens.
SellState-s have exact structure as Baskets so rebalance function is same for them.
(Optional, only when rebalance == true)
let txs:TransactionSignature[] =await basketsSdk.rebalanceBasket(sellState: Basket);
Step 3: Claim underlying tokens (or USDC) from SellState.
After this step, SellState is closed and rent is collected by user.
let txs:TransactionSignature[] =await basketsSdk.claimTokens(sellState: Basket);
Helpers:
// Find active SellStates for user that have been created after Step 1let sellStates:Basket[] =await basketsSdk.findActiveSellStates(user: PublicKey);
Automation
Automation on all baskets (Refilter/Reweight/Rebalance), as well as monitoring active
Buy/Sell states and rebalancing them is done by Symmetry Engine, based on RefilterInterval,
ReweightInterval, RebalanceInterval settings provided in basket rules.
Platforms and Developers can run their own automation for their baskets using examples below.
Managers can Force-(Refilter/Reweight/Rebalance) anytime.
Refilter Basket
Automatically selects assets based on provided rules in basket settings.
let txs:TransactionSignature=awaitbasketsSdk.refilterBasket(basket: Basket);
Reweight Basket
Automatically updates target weights for assets based on provided rules.
let txs:TransactionSignature=awaitbasketsSdk.reweightBasket(basket: Basket);
Rebalance Basket
Swap basket tokens using DEX aggregators to bring all tokens back to target weights.
let txs:TransactionSignature[] =awaitbasketsSdk.rebalanceBasket(basket: Basket);
Other Helper Functions
Set priority fee:
awaitbasketsSdk.setPriorityFee(lamports: number);
Fetch User's Holdings:
let allHoldings =awaitbasketsSdk.fetchAllHoldings(user: PublicKey);
Get Oracle Prices
let txs:number[] =awaitbasketsSdk.getOraclePrices();
Retrieve TransactionInstruction Objects that can be called on baskets.
import { BasketInstructions } from"@symmetry-hq/baskets-sdk";let ix:TransactionInstruction=awaitBasketInstructions.closeBasketIx(...);let ix:TransactionInstruction=awaitBasketInstructions.refilterBasketIx(...);let ix:TransactionInstruction=awaitBasketInstructions.reweightBasketIx(...);let ix:TransactionInstruction=awaitBasketInstructions.buyBasketWithSingleTokenIx(...);let ix:TransactionInstruction=awaitBasketInstructions.instantBurnIx(...);// and many more
Swap using Symmetry Baskets Liquidity
Users can swap and check available swap liquidity for a specific basket
Example shows how to create/buy/sell/rebalance/refilter/reweight a basket
Creating basket with following settings:
Manager fee : 0.1% - Users pay 0.1% to basket manager when buying
Is Mutable - Manager can edit basket settings and rules
Asset Pool consists of 4 tokens - [USDC, SOL, BTC, ETH]
Basket refilters underlying assets every week
Basket reweights underlying assets every day
Basket rebalances every 2 hours
Basket rebalances when target and current weights differ by at least 5%
Maximum allowed slippage on rebalance is 3% (compared to oracle price)
Basket provides swap liquidity for a token if current weight is within
5% (rebalance) 50%(lpOffset) = 2.5% of target weight
Basket has 1 rule:
Select TOP 3 assets by 1 week average MarketCap,
Weight chosen assets by square root of 1 day performance
Automation is on (Symmetry Engine will Refilter/Reweight/Rebalance based on rules)
Liquidity Provision is enabled.
import { BasketsSDK, Basket, BuyState, FilterType, FilterTime, SortBy, WeightType, WeightTime} from"@symmetry-hq/baskets-sdk";/* init baskets sdk */let basketsSdk:BasketsSDK=awaitBasketsSDK.init( connection, wallet);/* Create a basket (CreateBasket + SetRules + Refilter&Reweight) */let basket:Basket=awaitbasketsSdk.createBasket({ name:"Solana Bitcoin Ethereum", symbol:"SOLBTCETH", uri:"", hostPlatform:wallet.publicKey,// platform hosting UI hostPlatformFee:0,// fees on deposits for host manager:wallet.publicKey,// basket manager managerFee:10,// deposit fee in bps: 10 = 0.1% activelyManaged:1,// actively managed assetPool: [0,1,2,3],// USDC, SOL, BTC, ETH refilterInterval:7*24*3600,// 1 week reweightInterval:24*3600,// 1 day rebalanceInterval:2*3600,// 2 hours rebalanceThreshold:500,// 5% rebalanceSlippage:300,// 3% lpOffsetThreshold:5000,// 50% of rebalance threshold disableRebalance:0,// Auto Rebalance is on disableLp:0,// Liquidity provision is on rules: [{ filterBy:FilterType.MarketCap,// filter assets by marketcap filterDays:FilterTime.Week,// by 1 week average of marketcap sortBy:SortBy.DescendingOrder,// select top coins by marketcap totalWeight:100,// total weight of this rule fixedAsset:0,// (provide if filterBy is Fixed) numAssets:3,// select 3 assets weightBy:WeightType.Performance,// weight by performance weightDays:WeightTime.Day,// 1 day performance weightExpo:0.5,// square root of performance excludeAssets: [0],// Exclude USDC when filtering }],});/* 1. buy a basket with 50 USDC 2. rebalance buyState (buy underlying assets) 3. mint basket tokens */let buyState1:BuyState=awaitbasketsSdk.buyBasket(basket,50);let txsRebalanceBuyState1 =awaitbasketsSdk.rebalanceBuyState(buyState1);let txMintBasket1 =awaitbasketsSdk.mintBasket(buyState1);/* 1. buy a basket with 50 USDC without rebalancing unspent amount will have *penalty* and counted as if rebalance happend with maximum slippage (rebalanceSlippage = 3%) 2. mint basket tokens */let buyState2:BuyState=awaitbasketsSdk.buyBasket(basket,50);let txMintBasket2 =awaitbasketsSdk.mintBasket(buyState2);/* 1. sell a basket 2. rebalance basket (to USDC) 3. claim tokens (USDC + tokens for which rebalance failed) */let sellState1:Basket=awaitbasketsSdk.sellBasket(basket,0.4,true);let txsRebalanceSellState =awaitbasketsSdk.rebalanceBasket(sellState1);let txsClaimTokens1 =awaitbasketsSdk.claimTokens(sellState);/* 1. sell a basket without rebalancing 2. claim tokens (all tokens in a SellState) */let sellState2:Basket=awaitbasketsSdk.sellBasket(basket,0.4,false);let txsClaimTokens2 =awaitbasketsSdk.claimTokens(sellState2);/* 1. Refilter basket (RefilterInterval seconds since last refilter) 2. Reweight basket (ReweightInterval seconds since last reweight) 3. Rebalance basket (RebalanceInterval seconds since last rebalanace) */let txRefilter:TransactionSignature=awaitbasketsSdk.refilterBasket(basket);let txReweight:TransactionSignature=awaitbasketsSdk.reweightBasket(basket);let txsRebalance =awaitbasketsSdk.rebalanceBasket(basket);/* In some cases, when basket decides to remove token after refiltering, and rebalance function sells it to USDC, basket might end up with small amount of that token, for which it is unable to rebalance and sell the full amount. Although it's possible to remove the "dust" if user swaps equivalent amount of usdc (based on oracle price) for the token, which is stuck in a basket. Meaning that basket will receive USDC and user will receive token. RemoveDust will be triggered for tokens with equivalent USD value < 0.005. Amount of usdc to be swapped is computed in a following way: max(tokenAmount * tokenPrice * 1.1 (for slippage purposes), 0.000001) */let txsRemoveDust =awaitbasket.removeDust();
Types
// Information about Token and its settingstypeTokenSettings= { id:number, symbol:string, name:string, tokenMint:string, decimals:number, coingeckoId:string, pdaTokenAccount:string, oracleType:string, oracleAccount:string, oracleIndex:number, oracleConfidencePct:number, fixedConfidenceBps:number, tokenSwapFeeBeforeTwBps:number, tokenSwapFeeAfterTwBps:number, isLive:boolean, lpOn:boolean, useCurveData:boolean, additionalData:number[],}
// Create/Edit Basket settingstypeSimpleEditParams= { managerFee:number,// manager fee in bps 100 = 1% rebalanceInterval:number,// rebalance interval in seconds rebalanceThreshold:number,// rebalance threshold in bps 100 = 1% rebalanceSlippage:number,// rebalance slippage in bps 100 = 1% lpOffsetThreshold:number,// liquidity provsion ... in bps disableRebalance:boolean,// true for turning of automation disableLp:boolean,// true for turining of lp composition: {token:PublicKey, weight:number}[],}typeCreateBasketParams= { name:string, symbol:string, uri:string,// metadata URI which stores name, symbol, logo. hostPlatform:PublicKey,// platofrm hosting symmetry UI hostPlatformFee:number,// deposit fee for host platform in bps manager:PublicKey,// basket manager managerFee:number,// deposit fee for manager in bps activelyManaged:number,// 0 - Immutable, 1 - Mutable, 2 - Permissioned assetPool:number[],// Asset pool with tokenIds (check getTokenListData()) refilterInterval:number,// refilter interval in seconds reweightInterval:number,// reweight interval in seconds rebalanceInterval:number,// rebalance interval in seconds rebalanceThreshold:number,// rebalance threshold in bps rebalanceSlippage:number,// rebalance slippage in bps lpOffsetThreshold:number,// liquidity provision .. in bps disableRebalance:boolean,// true for disabling automaion disableLp:boolean,// true for disabling liquidity provision rules:Rule[],}typeRule= { totalWeight:number,// (integer) - total weight of this rule (relative to other rules) filterBy:FilterType,// FilterType.Fixed if you want this rule to have 1 specific asset filterDays:FilterTime, sortBy:SortBy, fixedAsset:number,// id of token if you want this rule to have 1 specific asset numAssets:number,// number of tokens for this rule (1 for specific asset) weightBy:WeightType, weightDays:WeightTime, weightExpo:number,// (integer) expo multiplied by 100x. for ^0.5 use 500 excludeAssets:number[],// token ids to be excluded when filtering}exportenumFilterType { Fixed,// 0 MarketCap,// 1 Volume,// 2 Performance,// 3}exportenumWeightType { Fixed,// 0 MarketCap,// 1 Volume,// 2 Performance,// 3}exportenumFilterTime { Day,// 0 Week,// 1 Month,// 2 Quarter,// 3 HalfYear,// 4 Year,// 5}exportenumWeightTime { Day,// 0 Week,// 1 Month,// 2 Quarter,// 3 HalfYear,// 4 Year,// 5}exportenumSortBy { DescendingOrder,// 0 AscendingOrder,// 1}