UNPKG

@symmetry-hq/baskets-v2-sdk

Version:

Symmetry Baskets V2 SDK

151 lines (150 loc) 7.04 kB
"use strict"; 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 }; }