@drift-labs/sdk
Version:
SDK for Drift Protocol
172 lines (171 loc) • 9.66 kB
JavaScript
"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;