@symmetry-hq/baskets-v2-sdk
Version:
Symmetry Baskets V2 SDK
400 lines (365 loc) • 14.5 kB
text/typescript
// Core dependencies
import { Program } from "@coral-xyz/anchor";
import { Connection, Keypair, MessageV0, PublicKey, TransactionMessage, TransactionSignature, VersionedTransaction } from "@solana/web3.js";
// Local imports
import { JUPITER_API_KEY, MAX_JUPITER_ACCOUNTS, PRIORITY_FEE, PYTHNET_CUSTODY_PRICE_SOL_ACCOUNT, PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT, USDC_DECIMALS, WSOL_DECIMALS } from "./utils/constants";
import { BasketsProgram } from "./idl/types";
import { getAccountInfos, getAllBaskets, getBasketsByCreator, getBasketsProgram, getPythSponsoredFeeds, getRandomSeed, getRaydiumCpmmPools, getRaydiumV4Pools, getWithdrawStateAccount, getWithdrawStatesByUser } from "./utils/programAccounts";
import { addLamportsForAutomationHandler, addNewTokenHandler, createBasketHandler, CreateBasketParams, createPythSponsoredFeedsHandler, EditBasketParams, editBasketSettingsHandler, removeTokenHandler, updatePythSponsoredFeedsHandler, updateTokenWeightsHandler } from "./basketManager";
import { buyBasketHandler, claimTokensHandler, sellBasketHandler, sellRebalanceHandler } from "./basketTrade";
import { rebalanceBasketTokensHandler, swapTokensHandler } from "./basketRebalance";
import { getQuoteResponseHandler } from "./instructions/jup";
import { fetchBasketState, getBasketTvl, parseBasketState, ParsedBasketState } from "./state/basket";
import { sendV0Transactions, VersionedTxs } from "./utils/txUtils";
import { BasketState } from "./state/basket";
import { fetchWithdrawState, ParsedWithdrawState, parseWithdrawState, WithdrawState } from "./state/withdrawState";
import { parseMetadata } from "./utils/metadataUtils";
import { loadOraclePrice, OracleType } from "./utils/oracle";
import { PoolInfo } from "./state/oracle";
import { parseRebalanceEvent } from "./utils/events";
export {
BasketState,
WithdrawState,
VersionedTxs,
}
export class BasketsSDK {
private sdkParams: {
payer: PublicKey,
connection: Connection,
program: Program<BasketsProgram>,
priorityFee: number,
jupiterApiKey: string;
maxAllowedAccounts: number;
};
constructor(params: {
connection: Connection,
payer?: PublicKey,
priorityFee?: number,
jupiterApiKey?: string,
maxAllowedAccounts?: number,
}) {
this.sdkParams = {
connection: params.connection,
payer: params.payer ?? Keypair.generate().publicKey,
program: getBasketsProgram(params.connection),
priorityFee: params.priorityFee ?? PRIORITY_FEE,
jupiterApiKey: params.jupiterApiKey ?? JUPITER_API_KEY,
maxAllowedAccounts: params.maxAllowedAccounts ?? MAX_JUPITER_ACCOUNTS,
};
}
async setPayer(payer: PublicKey) {
this.sdkParams.payer = payer;
}
async createBasket(params: CreateBasketParams): Promise<{
blockhash: string;
lastValidBlockHeight: number;
versionedTxs: VersionedTransaction[];
batches: number[];
address: PublicKey;
}> {
const basketKeypair = Keypair.generate();
return {
...(await createBasketHandler(this.sdkParams, params, basketKeypair)),
address: basketKeypair.publicKey,
};
}
async editBasketSettings(params: EditBasketParams): Promise<VersionedTxs> {
return await editBasketSettingsHandler(this.sdkParams, params);
}
async addLamportsForAutomation(params: {
basket: PublicKey;
amount: number;
}): Promise<VersionedTxs> {
return await addLamportsForAutomationHandler(this.sdkParams, params);
}
async findPoolsForToken(params: {
token: PublicKey;
}): Promise<PoolInfo[]> {
const oracleAccounts: PublicKey[] = [
PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT,
PYTHNET_CUSTODY_PRICE_SOL_ACCOUNT,
];
const oracleAccountInfos = await getAccountInfos(this.sdkParams.connection, oracleAccounts);
const usdcPrice = loadOraclePrice(USDC_DECIMALS, OracleType.Pyth, oracleAccountInfos[0], null, 0, 0, 0).avgPrice;
const solPrice = loadOraclePrice(WSOL_DECIMALS, OracleType.Pyth, oracleAccountInfos[1], null, 0, 0, 0).avgPrice;
const raydiumV4Pools = await getRaydiumV4Pools(this.sdkParams.connection, params.token, usdcPrice, solPrice);
const raydiumCpmmPools = await getRaydiumCpmmPools(this.sdkParams.connection, params.token, usdcPrice, solPrice);
const pythSponsoredFeeds = await getPythSponsoredFeeds(this.sdkParams.program, params.token);
const allPools = [...raydiumV4Pools, ...raydiumCpmmPools, ...pythSponsoredFeeds];
allPools.sort((a, b) => b.liquidity - a.liquidity);
return allPools;
}
async createPythSponsoredFeed(): Promise<VersionedTxs> {
return await createPythSponsoredFeedsHandler(this.sdkParams);
}
async updatePythSponsoredFeed(params: {
tokenMint: PublicKey,
feedAccount: PublicKey,
isActive: boolean,
}): Promise<VersionedTxs> {
return await updatePythSponsoredFeedsHandler(this.sdkParams, params);
}
async addNewToken(params: {
basket: PublicKey;
token: PublicKey;
tokenWeight: number;
oracleType: number;
oraclePool: PublicKey;
oracle1: PublicKey;
oracle2: PublicKey;
}): Promise<VersionedTxs> {
return await addNewTokenHandler(this.sdkParams, params);
}
async removeToken(params: {
basket: PublicKey;
token: PublicKey;
}): Promise<VersionedTxs> {
return await removeTokenHandler(this.sdkParams, params);
}
async updateTokenWeights(params: {
basket: PublicKey;
tokenWeights: number[];
writeVersion: number;
}): Promise<VersionedTxs> {
return await updateTokenWeightsHandler(this.sdkParams, params);
}
async buyBasketBackend(params: {
basket: PublicKey,
depositAmount: number,
depositMint: PublicKey,
}): Promise<VersionedTxs> {
return await buyBasketHandler(this.sdkParams, params);
}
async buyBasket(params: {
basket: PublicKey,
depositAmount: number,
depositMint: PublicKey,
}): Promise<VersionedTxs> {
const response = await fetch(
`https://api.symmetry.fi/baskets/v2/tx/buy` +
`?payer=${this.sdkParams.payer.toBase58()}` +
`&basket=${params.basket.toBase58()}` +
`&depositMint=${params.depositMint.toBase58()}` +
`&depositAmount=${params.depositAmount}` +
`&priorityFee=${this.sdkParams.priorityFee}`
);
const data = await response.json();
if (data.error)
throw new Error(data.error);
const { blockhash, lastValidBlockHeight, versionedTxs: rawTxs, batches } = data;
const versionedTxs = rawTxs.map(
(tx: any) => VersionedTransaction.deserialize(
Uint8Array.from(Buffer.from(tx, "base64"))
)
);
return {
blockhash,
lastValidBlockHeight,
versionedTxs,
batches,
};
}
async sellBasketBackend(params: {
basket: PublicKey;
amountToWithdraw: number,
destinationMint: PublicKey,
rebalance: boolean,
}): Promise<{
blockhash: string;
lastValidBlockHeight: number;
versionedTxs: VersionedTransaction[];
batches: number[];
address: PublicKey;
}> {
const withdrawStateSeed = getRandomSeed();
const address = getWithdrawStateAccount(withdrawStateSeed);
return {
...(await sellBasketHandler(this.sdkParams, {
...params,
withdrawStateSeed,
})),
address,
};
}
async sellBasket(params: {
basket: PublicKey;
amountToWithdraw: number,
destinationMint: PublicKey,
rebalance: boolean,
}): Promise<{
blockhash: string;
lastValidBlockHeight: number;
versionedTxs: VersionedTransaction[];
batches: number[];
address: PublicKey;
}> {
const response = await fetch(
`https://api.symmetry.fi/baskets/v2/tx/sell` +
`?payer=${this.sdkParams.payer.toBase58()}` +
`&basket=${params.basket.toBase58()}` +
`&amountToWithdraw=${params.amountToWithdraw}` +
`&destinationMint=${params.destinationMint.toBase58()}` +
`&rebalance=${params.rebalance}` +
`&priorityFee=${this.sdkParams.priorityFee}`
);
const data = await response.json();
if (data.error)
throw new Error(data.error);
const { address, blockhash, lastValidBlockHeight, versionedTxs: rawTxs, batches } = data;
const versionedTxs = rawTxs.map(
(tx: any) => VersionedTransaction.deserialize(
Uint8Array.from(Buffer.from(tx, "base64"))
)
);
return {
blockhash,
lastValidBlockHeight,
versionedTxs,
batches,
address,
};
}
async rebalanceSellState(params: {
withdrawState: PublicKey;
}): Promise<VersionedTxs> {
return await sellRebalanceHandler(this.sdkParams, params);
}
async claimTokens(params: {
withdrawState: PublicKey;
}): Promise<VersionedTxs> {
return await claimTokensHandler(this.sdkParams, params);
}
async generateSwapQuote(params: {
fromToken: PublicKey;
toToken: PublicKey;
amount: number;
slippageBps: number;
}): Promise<any> {
return await getQuoteResponseHandler({
jupiterApiKey: this.sdkParams.jupiterApiKey,
maxAllowedAccounts: this.sdkParams.maxAllowedAccounts,
...params,
});
}
async swapTokens(params: {
basket: PublicKey;
fromToken: PublicKey;
toToken: PublicKey;
fromAmount: number;
quoteResponse: any;
fromTokenWeight?: number;
toTokenWeight?: number;
}): Promise<VersionedTxs> {
return await swapTokensHandler(this.sdkParams, params);
}
async rebalanceBasketTokensBackend(params: {
basket: PublicKey;
fromToken?: PublicKey;
toToken?: PublicKey;
minSwapValue?: number;
maxSellValuePerToken?: number;
maxNumberOfSwaps?: number;
}): Promise<VersionedTxs> {
return await rebalanceBasketTokensHandler(this.sdkParams, params);
}
async rebalanceBasketTokens(params: {
basket: PublicKey;
fromToken?: PublicKey;
toToken?: PublicKey;
minSwapValue?: number;
maxSellValuePerToken?: number;
maxNumberOfSwaps?: number;
}): Promise<VersionedTxs> {
let str = `https://api.symmetry.fi/baskets/v2/tx/rebalance` +
`?payer=${this.sdkParams.payer.toBase58()}` +
`&basket=${params.basket.toBase58()}` +
`&priorityFee=${this.sdkParams.priorityFee}`;
if (params.fromToken)
str += `&fromToken=${params.fromToken.toBase58()}`;
if (params.toToken)
str += `&toToken=${params.toToken.toBase58()}`;
if (params.minSwapValue)
str += `&minSwapValue=${params.minSwapValue}`;
if (params.maxSellValuePerToken)
str += `&maxSellValuePerToken=${params.maxSellValuePerToken}`;
if (params.maxNumberOfSwaps)
str += `&maxNumberOfSwaps=${params.maxNumberOfSwaps}`;
const response = await fetch(str);
const data = await response.json();
if (data.error)
throw new Error(data.error);
const { blockhash, lastValidBlockHeight, versionedTxs: rawTxs, batches } = data;
const versionedTxs = rawTxs.map(
(tx: any) => VersionedTransaction.deserialize(
Uint8Array.from(Buffer.from(tx, "base64"))
)
);
return {
blockhash,
lastValidBlockHeight,
versionedTxs,
batches,
};
}
async getBasket(params: {
basket: PublicKey;
requestTvl?: boolean;
}): Promise<ParsedBasketState> {
const basketState = await fetchBasketState(this.sdkParams.program, params.basket);
const metadata = await parseMetadata(this.sdkParams.connection, basketState.metadataAccount);
let tvl = null, tokenValues = null;
if (params.requestTvl)
({tvl, tokenValues} = await getBasketTvl(this.sdkParams.program, basketState));
return {
...parseBasketState(basketState),
metadata: metadata,
tokenValues: tokenValues,
tvl: tvl,
}
}
async getWithdrawState(params: {
withdrawState: PublicKey;
}): Promise<ParsedWithdrawState> {
const withdrawStateData = await fetchWithdrawState(this.sdkParams.program, params.withdrawState);
return parseWithdrawState(withdrawStateData);
}
async getAllBaskets(): Promise<ParsedBasketState[]> {
return await getAllBaskets(this.sdkParams.program);
}
async getBasketsByCreator(
creator: PublicKey,
): Promise<ParsedBasketState[]> {
return await getBasketsByCreator(this.sdkParams.program, creator);
}
async getWithdrawStatesByUser(
user: PublicKey,
): Promise<ParsedWithdrawState[]> {
return await getWithdrawStatesByUser(this.sdkParams.program, user);
}
async sendSignedVersionedTxs(
txs: VersionedTxs,
simulateTransactions: boolean = false,
): Promise<TransactionSignature[]> {
return await sendV0Transactions(
this.sdkParams.connection,
txs,
simulateTransactions
);
}
addSwapListener(
callback: (event: any, slot: number, signature: string) => void
) {
return this.sdkParams.program.addEventListener(
"rebalance",
(event, slot, signature) => {
let processedEvent = parseRebalanceEvent(event);
try { callback(processedEvent, slot, signature) } catch (e: any) {
console.log(e.message);
}
}
);
}
removeEventListener(id: number) {
this.sdkParams.program.removeEventListener(id);
}
}