@symmetry-hq/baskets-v2-sdk
Version:
Symmetry Baskets V2 SDK
151 lines (150 loc) • 7.04 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OracleTypeUtils = exports.OracleType = void 0;
exports.validateOracle = validateOracle;
exports.loadOraclePrice = loadOraclePrice;
// Core dependencies
const spl_token_1 = require("@solana/spl-token");
const web3_js_1 = require("@solana/web3.js");
// Local imports
const constants_1 = require("./constants");
var OracleType;
(function (OracleType) {
OracleType[OracleType["Pyth"] = 0] = "Pyth";
OracleType[OracleType["RaydiumCpmm"] = 1] = "RaydiumCpmm";
OracleType[OracleType["RaydiumLiquidityPoolV4"] = 2] = "RaydiumLiquidityPoolV4";
OracleType[OracleType["Unknown"] = 3] = "Unknown";
})(OracleType || (exports.OracleType = OracleType = {}));
class OracleTypeUtils {
static toU8(type) {
switch (type) {
case OracleType.Pyth: return 0;
case OracleType.RaydiumCpmm: return 1;
case OracleType.RaydiumLiquidityPoolV4: return 2;
case OracleType.Unknown: return 3;
}
}
static fromU8(value) {
switch (value) {
case 0: return OracleType.Pyth;
case 1: return OracleType.RaydiumCpmm;
case 2: return OracleType.RaydiumLiquidityPoolV4;
default: return OracleType.Unknown;
}
}
}
exports.OracleTypeUtils = OracleTypeUtils;
function validateOracle(program, tokenMint, tokenMintAccountInfo, oracleAccountInfo, oracleType, oracle1, oracle2) {
const oracleTypeObj = OracleTypeUtils.fromU8(oracleType);
// Validate token mint
if (!tokenMintAccountInfo.owner.equals(spl_token_1.TOKEN_PROGRAM_ID))
return "Invalid token mint owner";
if (tokenMintAccountInfo.data.length !== 82)
return "Invalid token mint account size";
if (oracleTypeObj == OracleType.Pyth) {
const pythSponsoredFeeds = program.coder.accounts.decode("pythSponsoredFeeds", oracleAccountInfo.data);
let index = 0;
for (let i = 0; i < pythSponsoredFeeds.numTokens; i++)
if (pythSponsoredFeeds.mints[i].equals(tokenMint)) {
index = i;
break;
}
if (!pythSponsoredFeeds.mints[index].equals(tokenMint))
return "Token mint not found in PythSponsoredFeeds";
if (!pythSponsoredFeeds.feeds[index].equals(oracle1))
return "Invalid pyth oracle feed";
if (pythSponsoredFeeds.isActive[index] == 0)
return "Pyth oracle feed is not active";
return "OK";
}
if (oracleTypeObj == OracleType.RaydiumCpmm || oracleTypeObj == OracleType.RaydiumLiquidityPoolV4) {
const accountData = oracleAccountInfo.data;
// Check status for V4 pools
if (oracleTypeObj == OracleType.RaydiumLiquidityPoolV4) {
const status = accountData[0];
if (status !== 6)
return "Invalid status (Using OrderBook)";
}
// Get token vault and mint offsets based on pool type
const [vaultOffset0, vaultOffset1, mintOffset0, mintOffset1] = oracleTypeObj == OracleType.RaydiumCpmm ? [72, 104, 168, 200] : [336, 368, 400, 432];
// Extract token vaults and mints
let tokenVault0 = new web3_js_1.PublicKey(accountData.slice(vaultOffset0, vaultOffset0 + 32));
let tokenVault1 = new web3_js_1.PublicKey(accountData.slice(vaultOffset1, vaultOffset1 + 32));
let tokenMint0 = new web3_js_1.PublicKey(accountData.slice(mintOffset0, mintOffset0 + 32));
let tokenMint1 = new web3_js_1.PublicKey(accountData.slice(mintOffset1, mintOffset1 + 32));
// Swap if needed to ensure tokenMint1 is SOL/USDC
if (tokenMint0.equals(constants_1.WSOL_MINT) || tokenMint0.equals(constants_1.USDC_MINT) || tokenMint0.equals(constants_1.MEME_SOL)) {
[tokenMint0, tokenMint1] = [tokenMint1, tokenMint0];
[tokenVault0, tokenVault1] = [tokenVault1, tokenVault0];
}
// Validate owner program
const expectedOwner = oracleTypeObj == OracleType.RaydiumCpmm ? constants_1.RAYDIUM_CPMM : constants_1.RAYDIUM_LIQUIDITY_POOL_V4;
if (!oracleAccountInfo.owner.equals(expectedOwner))
return "Invalid oracle account owner";
// Validate token mints and vaults
if (!tokenMint0.equals(tokenMint))
return "Invalid base token mint";
if (!tokenMint1.equals(constants_1.WSOL_MINT) && !tokenMint1.equals(constants_1.USDC_MINT) && !tokenMint1.equals(constants_1.MEME_SOL)) {
return "Invalid quote token mint (Should be SOL, MEME-SOL or USDC)";
}
if (!tokenVault0.equals(oracle1))
return "Invalid base token vault";
if (!tokenVault1.equals(oracle2))
return "Invalid quote token vault";
return "OK";
}
return "Invalid oracle type";
}
function loadOraclePrice(tokenDecimals, oracleType, oracleAccount1, oracleAccount2, solPrice, usdcPrice, tokenAmount) {
let avgPrice;
let confidence;
let sellValue;
if (!oracleAccount1) {
throw new Error();
}
switch (OracleTypeUtils.fromU8(oracleType)) {
case OracleType.Pyth: {
const accountData = oracleAccount1.data;
const price = accountData.readBigInt64LE(73);
const exp = accountData.readInt32LE(89);
avgPrice = Number(price) * 10 ** exp;
confidence = avgPrice / 100;
const sellPrice = avgPrice - confidence;
const powNum = 10 ** tokenDecimals;
sellValue = tokenAmount * sellPrice / powNum;
break;
}
case OracleType.RaydiumCpmm:
case OracleType.RaydiumLiquidityPoolV4: {
if (!oracleAccount2) {
throw new Error();
}
const baseData = spl_token_1.AccountLayout.decode(oracleAccount1.data);
const quoteData = spl_token_1.AccountLayout.decode(oracleAccount2.data);
const x = parseInt(baseData.amount.toString());
const y = parseInt(quoteData.amount.toString());
const quoteMint = quoteData.mint;
const [quoteDecimals, quotePrice] = (quoteMint.equals(constants_1.WSOL_MINT) || quoteMint.equals(constants_1.MEME_SOL))
? [constants_1.WSOL_DECIMALS, solPrice]
: [constants_1.USDC_DECIMALS, usdcPrice];
const priceRaw = (y * quotePrice) / x;
const tokenDecimalsPow = 10 ** tokenDecimals;
const quoteDecimalsPow = 10 ** quoteDecimals;
avgPrice = (priceRaw * tokenDecimalsPow) / quoteDecimalsPow;
confidence = avgPrice / 100;
const baseAmount = (tokenAmount * y) / (x + tokenAmount);
const powNum = 10 ** tokenDecimals;
sellValue = baseAmount * quotePrice / powNum;
break;
}
default:
throw new Error();
}
return {
sellPrice: avgPrice - confidence,
avgPrice,
buyPrice: avgPrice + confidence,
age: 0,
sellValue
};
}