UNPKG

@drift-labs/sdk

Version:
172 lines (171 loc) • 9.66 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMultipleBetweenOracleSources = exports.trimVaaSignatures = exports.getNewOracleConfPct = exports.calculateLiveOracleStd = exports.calculateLiveOracleTwap = exports.isOracleTooDivergent = exports.isOracleValid = exports.getMaxConfidenceIntervalMultiplier = exports.oraclePriceBands = void 0; const types_1 = require("../types"); const numericConstants_1 = require("../constants/numericConstants"); const assert_1 = require("../assert/assert"); const anchor_1 = require("@coral-xyz/anchor"); function oraclePriceBands(market, oraclePriceData) { const maxPercentDiff = market.marginRatioInitial - market.marginRatioMaintenance; const offset = oraclePriceData.price .mul(new anchor_1.BN(maxPercentDiff)) .div(numericConstants_1.MARGIN_PRECISION); (0, assert_1.assert)(offset.gte(numericConstants_1.ZERO)); return [oraclePriceData.price.sub(offset), oraclePriceData.price.add(offset)]; } exports.oraclePriceBands = oraclePriceBands; function getMaxConfidenceIntervalMultiplier(market) { let maxConfidenceIntervalMultiplier; if ((0, types_1.isVariant)(market.contractTier, 'a')) { maxConfidenceIntervalMultiplier = new anchor_1.BN(1); } else if ((0, types_1.isVariant)(market.contractTier, 'b')) { maxConfidenceIntervalMultiplier = new anchor_1.BN(1); } else if ((0, types_1.isVariant)(market.contractTier, 'c')) { maxConfidenceIntervalMultiplier = new anchor_1.BN(2); } else if ((0, types_1.isVariant)(market.contractTier, 'speculative')) { maxConfidenceIntervalMultiplier = new anchor_1.BN(10); } else { maxConfidenceIntervalMultiplier = new anchor_1.BN(50); } return maxConfidenceIntervalMultiplier; } exports.getMaxConfidenceIntervalMultiplier = getMaxConfidenceIntervalMultiplier; function isOracleValid(market, oraclePriceData, oracleGuardRails, slot) { // checks if oracle is valid for an AMM only fill const amm = market.amm; const isOraclePriceNonPositive = oraclePriceData.price.lte(numericConstants_1.ZERO); const isOraclePriceTooVolatile = oraclePriceData.price .div(anchor_1.BN.max(numericConstants_1.ONE, amm.historicalOracleData.lastOraclePriceTwap)) .gt(oracleGuardRails.validity.tooVolatileRatio) || amm.historicalOracleData.lastOraclePriceTwap .div(anchor_1.BN.max(numericConstants_1.ONE, oraclePriceData.price)) .gt(oracleGuardRails.validity.tooVolatileRatio); const maxConfidenceIntervalMultiplier = getMaxConfidenceIntervalMultiplier(market); const isConfidenceTooLarge = anchor_1.BN.max(numericConstants_1.ONE, oraclePriceData.confidence) .mul(numericConstants_1.BID_ASK_SPREAD_PRECISION) .div(oraclePriceData.price) .gt(oracleGuardRails.validity.confidenceIntervalMaxSize.mul(maxConfidenceIntervalMultiplier)); const oracleIsStale = new anchor_1.BN(slot) .sub(oraclePriceData.slot) .gt(oracleGuardRails.validity.slotsBeforeStaleForAmm); return !(!oraclePriceData.hasSufficientNumberOfDataPoints || oracleIsStale || isOraclePriceNonPositive || isOraclePriceTooVolatile || isConfidenceTooLarge); } exports.isOracleValid = isOracleValid; function isOracleTooDivergent(amm, oraclePriceData, oracleGuardRails, now) { const sinceLastUpdate = now.sub(amm.historicalOracleData.lastOraclePriceTwapTs); const sinceStart = anchor_1.BN.max(numericConstants_1.ZERO, numericConstants_1.FIVE_MINUTE.sub(sinceLastUpdate)); const oracleTwap5min = amm.historicalOracleData.lastOraclePriceTwap5Min .mul(sinceStart) .add(oraclePriceData.price) .mul(sinceLastUpdate) .div(sinceStart.add(sinceLastUpdate)); const oracleSpread = oracleTwap5min.sub(oraclePriceData.price); const oracleSpreadPct = oracleSpread.mul(numericConstants_1.PRICE_PRECISION).div(oracleTwap5min); const maxDivergence = anchor_1.BN.max(oracleGuardRails.priceDivergence.markOraclePercentDivergence, numericConstants_1.PERCENTAGE_PRECISION.div(new anchor_1.BN(10))); const tooDivergent = oracleSpreadPct.abs().gte(maxDivergence); return tooDivergent; } exports.isOracleTooDivergent = isOracleTooDivergent; function calculateLiveOracleTwap(histOracleData, oraclePriceData, now, period) { let oracleTwap = undefined; if (period.eq(numericConstants_1.FIVE_MINUTE)) { oracleTwap = histOracleData.lastOraclePriceTwap5Min; } else { //todo: assumes its fundingPeriod (1hr) // period = amm.fundingPeriod; oracleTwap = histOracleData.lastOraclePriceTwap; } const sinceLastUpdate = anchor_1.BN.max(numericConstants_1.ONE, now.sub(histOracleData.lastOraclePriceTwapTs)); const sinceStart = anchor_1.BN.max(numericConstants_1.ZERO, period.sub(sinceLastUpdate)); const clampRange = oracleTwap.div(new anchor_1.BN(3)); const clampedOraclePrice = anchor_1.BN.min(oracleTwap.add(clampRange), anchor_1.BN.max(oraclePriceData.price, oracleTwap.sub(clampRange))); const newOracleTwap = oracleTwap .mul(sinceStart) .add(clampedOraclePrice.mul(sinceLastUpdate)) .div(sinceStart.add(sinceLastUpdate)); return newOracleTwap; } exports.calculateLiveOracleTwap = calculateLiveOracleTwap; function calculateLiveOracleStd(amm, oraclePriceData, now) { const sinceLastUpdate = anchor_1.BN.max(numericConstants_1.ONE, now.sub(amm.historicalOracleData.lastOraclePriceTwapTs)); const sinceStart = anchor_1.BN.max(numericConstants_1.ZERO, amm.fundingPeriod.sub(sinceLastUpdate)); const liveOracleTwap = calculateLiveOracleTwap(amm.historicalOracleData, oraclePriceData, now, amm.fundingPeriod); const liveOracleTwap5MIN = calculateLiveOracleTwap(amm.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE); const priceDeltaVsTwap = anchor_1.BN.max(oraclePriceData.price.sub(liveOracleTwap).abs(), oraclePriceData.price.sub(liveOracleTwap5MIN).abs()); const oracleStd = priceDeltaVsTwap.add(amm.oracleStd.mul(sinceStart).div(sinceStart.add(sinceLastUpdate))); return oracleStd; } exports.calculateLiveOracleStd = calculateLiveOracleStd; function getNewOracleConfPct(amm, oraclePriceData, reservePrice, now) { const confInterval = oraclePriceData.confidence || numericConstants_1.ZERO; const sinceLastUpdate = anchor_1.BN.max(numericConstants_1.ZERO, now.sub(amm.historicalOracleData.lastOraclePriceTwapTs)); let lowerBoundConfPct = amm.lastOracleConfPct; if (sinceLastUpdate.gt(numericConstants_1.ZERO)) { const lowerBoundConfDivisor = anchor_1.BN.max(new anchor_1.BN(21).sub(sinceLastUpdate), new anchor_1.BN(5)); lowerBoundConfPct = amm.lastOracleConfPct.sub(amm.lastOracleConfPct.div(lowerBoundConfDivisor)); } const confIntervalPct = confInterval .mul(numericConstants_1.BID_ASK_SPREAD_PRECISION) .div(reservePrice); const confIntervalPctResult = anchor_1.BN.max(confIntervalPct, lowerBoundConfPct); return confIntervalPctResult; } exports.getNewOracleConfPct = getNewOracleConfPct; function trimVaaSignatures(vaa, n = 3) { const currentNumSignatures = vaa[5]; if (n > currentNumSignatures) { throw new Error("Resulting VAA can't have more signatures than the original VAA"); } const trimmedVaa = Buffer.concat([ vaa.subarray(0, 6 + n * 66), vaa.subarray(6 + currentNumSignatures * 66), ]); trimmedVaa[5] = n; return trimmedVaa; } exports.trimVaaSignatures = trimVaaSignatures; function getMultipleBetweenOracleSources(firstOracleSource, secondOracleSource) { if ((0, types_1.isVariant)(firstOracleSource, 'pythPull') && (0, types_1.isVariant)(secondOracleSource, 'pyth1MPull')) { return { numerator: new anchor_1.BN(1000000), denominator: new anchor_1.BN(1) }; } if ((0, types_1.isVariant)(firstOracleSource, 'pythPull') && (0, types_1.isVariant)(secondOracleSource, 'pyth1KPull')) { return { numerator: new anchor_1.BN(1000), denominator: new anchor_1.BN(1) }; } if ((0, types_1.isVariant)(firstOracleSource, 'pyth1MPull') && (0, types_1.isVariant)(secondOracleSource, 'pythPull')) { return { numerator: new anchor_1.BN(1), denominator: new anchor_1.BN(1000000) }; } if ((0, types_1.isVariant)(firstOracleSource, 'pyth1KPull') && (0, types_1.isVariant)(secondOracleSource, 'pythPull')) { return { numerator: new anchor_1.BN(1), denominator: new anchor_1.BN(1000) }; } if ((0, types_1.isVariant)(firstOracleSource, 'pythLazer') && (0, types_1.isVariant)(secondOracleSource, 'pythLazer1M')) { return { numerator: new anchor_1.BN(1000000), denominator: new anchor_1.BN(1) }; } if ((0, types_1.isVariant)(firstOracleSource, 'pythLazer') && (0, types_1.isVariant)(secondOracleSource, 'pythLazer1K')) { return { numerator: new anchor_1.BN(1000), denominator: new anchor_1.BN(1) }; } if ((0, types_1.isVariant)(firstOracleSource, 'pythLazer1M') && (0, types_1.isVariant)(secondOracleSource, 'pythLazer')) { return { numerator: new anchor_1.BN(1), denominator: new anchor_1.BN(1000000) }; } if ((0, types_1.isVariant)(firstOracleSource, 'pythLazer1K') && (0, types_1.isVariant)(secondOracleSource, 'pythLazer')) { return { numerator: new anchor_1.BN(1), denominator: new anchor_1.BN(1000) }; } return { numerator: new anchor_1.BN(1), denominator: new anchor_1.BN(1) }; } exports.getMultipleBetweenOracleSources = getMultipleBetweenOracleSources;