"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StubOracle = exports.OracleProvider = exports.sbOnDemandProgram = exports.SOL_MINT_MAINNET = exports.USDC_MINT_MAINNET = void 0;
exports.parseSwitchboardOracleV1 = parseSwitchboardOracleV1;
exports.switchboardDecimalToBig = switchboardDecimalToBig;
exports.parseSwitchboardOracleV2 = parseSwitchboardOracleV2;
exports.getStandardDeviation = getStandardDeviation;
exports.parseSwitchboardOnDemandOracle = parseSwitchboardOnDemandOracle;
exports.parseSwitchboardOracle = parseSwitchboardOracle;
exports.isSwitchboardOracle = isSwitchboardOracle;
exports.isPythOracle = isPythOracle;
exports.isOrcaOracle = isOrcaOracle;
exports.isRaydiumOracle = isRaydiumOracle;
exports.isClmmOracle = isClmmOracle;
exports.parsePythOracle = parsePythOracle;
exports.isOracleStaleOrUnconfident = isOracleStaleOrUnconfident;
exports.deriveFallbackOracleQuoteKey = deriveFallbackOracleQuoteKey;
exports.createFallbackOracleMap = createFallbackOracleMap;
const anchor_1 = require("@coral-xyz/anchor");
const client_1 = require("@pythnetwork/client");
const web3_js_1 = require("@solana/web3.js");
const on_demand_1 = require("@switchboard-xyz/on-demand");
const sbv2_lite_1 = __importDefault(require("@switchboard-xyz/sbv2-lite"));
const big_js_1 = __importDefault(require("big.js"));
const switchboard_anchor_1 = require("switchboard-anchor");
const constants_1 = require("../constants");
const I80F48_1 = require("../numbers/I80F48");
const utils_1 = require("../utils");
const pyth_solana_receiver_1 = require("./pyth_solana_receiver");
const SBV1_DEVNET_PID = new web3_js_1.PublicKey('7azgmy1pFXHikv36q1zZASvFq5vFa39TT9NweVugKKTU');
const SBV1_MAINNET_PID = new web3_js_1.PublicKey('DtmE9D2CSB4L5D6A15mraeEjrGMm6auWVzgaD8hK2tZM');
const ORCA_MAINNET_PID = new web3_js_1.PublicKey('whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc');
const ORCA_WHIRLPOOL_LEN = 653;
const ORCA_WHIRLPOOL_DISCRIMINATOR = [63, 149, 209, 12, 225, 128, 99, 9];
const RAYDIUM_MAINNET_PID = new web3_js_1.PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK');
const RAYDIUM_POOL_LEN = 1544;
const RAYDIUM_POOL_DISCRIMINATOR = [247, 237, 227, 245, 215, 195, 222, 70];
exports.USDC_MINT_MAINNET = new web3_js_1.PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
exports.SOL_MINT_MAINNET = new web3_js_1.PublicKey('So11111111111111111111111111111111111111112');
let sbv2DevnetProgram;
let sbv2MainnetProgram;
let pythSolanaReceiverProgram;
var OracleProvider;
(function (OracleProvider) {
    OracleProvider[OracleProvider["Pyth"] = 0] = "Pyth";
    OracleProvider[OracleProvider["PythV2"] = 1] = "PythV2";
    OracleProvider[OracleProvider["Switchboard"] = 2] = "Switchboard";
    OracleProvider[OracleProvider["SwitchboardOnDemand"] = 3] = "SwitchboardOnDemand";
    OracleProvider[OracleProvider["Stub"] = 4] = "Stub";
})(OracleProvider || (exports.OracleProvider = OracleProvider = {}));
class StubOracle {
    publicKey;
    group;
    mint;
    lastUpdateTs;
    lastUpdateSlot;
    price;
    deviation;
    static from(publicKey, obj) {
        return new StubOracle(publicKey, obj.group, obj.mint, obj.price, obj.lastUpdateTs, obj.lastUpdateSlot, obj.deviation);
    }
    constructor(publicKey, group, mint, price, lastUpdateTs, lastUpdateSlot, deviation) {
        this.publicKey = publicKey;
        this.group = group;
        this.mint = mint;
        this.lastUpdateTs = lastUpdateTs;
        this.lastUpdateSlot = lastUpdateSlot;
        this.price = I80F48_1.I80F48.from(price);
        this.deviation = I80F48_1.I80F48.from(deviation);
    }
}
exports.StubOracle = StubOracle;
// https://gist.github.com/microwavedcola1/b741a11e6ee273a859f3ef00b35ac1f0
function parseSwitchboardOracleV1(accountInfo) {
    const price = accountInfo.data.readDoubleLE(1 + 32 + 4 + 4);
    const lastUpdatedSlot = parseInt(accountInfo.data.readBigUInt64LE(1 + 32 + 4 + 4 + 8).toString());
    const minResponse = accountInfo.data.readDoubleLE(1 + 32 + 4 + 4 + 8 + 8 + 8);
    const maxResponse = accountInfo.data.readDoubleLE(1 + 32 + 4 + 4 + 8 + 8 + 8 + 8);
    return {
        price,
        lastUpdatedSlot,
        uiDeviation: maxResponse - minResponse,
        provider: OracleProvider.Switchboard,
    };
}
function switchboardDecimalToBig(sbDecimal) {
    const mantissa = new big_js_1.default(sbDecimal.mantissa.toString());
    const scale = sbDecimal.scale;
    const oldDp = big_js_1.default.DP;
    big_js_1.default.DP = 20;
    const result = mantissa.div(new big_js_1.default(10).pow(scale));
    big_js_1.default.DP = oldDp;
    return result;
}
function parseSwitchboardOracleV2(program, accountInfo, oracle) {
    try {
        //
        const price = program.decodeLatestAggregatorValue(accountInfo).toNumber();
        const lastUpdatedSlot = program
            .decodeAggregator(accountInfo)
            .latestConfirmedRound.roundOpenSlot.toNumber();
        const stdDeviation = switchboardDecimalToBig(program.decodeAggregator(accountInfo).latestConfirmedRound.stdDeviation);
        return {
            price,
            lastUpdatedSlot,
            uiDeviation: stdDeviation.toNumber(),
            provider: OracleProvider.Switchboard,
        };
        // if oracle is badly configured or didn't publish price at least once
        // decodeLatestAggregatorValue can throw (0 switchboard rounds).
    }
    catch (e) {
        console.log(`Unable to parse Switchboard Oracle V2: ${oracle}`, e);
        return {
            price: 0,
            lastUpdatedSlot: 0,
            uiDeviation: 0,
            provider: OracleProvider.Switchboard,
        };
    }
}
function getStandardDeviation(array) {
    const n = array.length;
    const mean = array.reduce((a, b) => a + b) / n;
    return Math.sqrt(array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
}
function parseSwitchboardOnDemandOracle(program, accountInfo, oracle) {
    try {
        const decodedPullFeed = program.coder.accounts.decode('pullFeedAccountData', accountInfo.data);
        // useful for development
        // console.log(decodedPullFeed);
        // console.log(decodedPullFeed.submissions);
        // Use custom code instead of toFeedValue from sb on demand sdk
        // Custom code which has uses min sample size
        // const feedValue = toFeedValue(decodedPullFeed.submissions, new BN(0));
        // filter empty and sort by slot to get latest submissions in slice later
        const submissions = decodedPullFeed.submissions
            .filter((s) => s.slot != 0)
            .sort((x, y) => y.slot - x.slot);
        // submissions.map((s) => console.log(`- ${s.slot}`));
        let values = submissions.slice(0, decodedPullFeed.minSampleSize);
        if (values.length === 0) {
            return {
                price: 0,
                lastUpdatedSlot: 0,
                uiDeviation: 0,
                provider: OracleProvider.SwitchboardOnDemand,
            };
        }
        values = values.sort((x, y) => (x.value.lt(y.value) ? -1 : 1));
        const feedValue = values[Math.floor(values.length / 2)];
        const price = new big_js_1.default(feedValue.value.toString()).div(1e18);
        const lastUpdatedSlot = feedValue.slot.toNumber();
        const stdDeviation = getStandardDeviation(values.map((val) => new big_js_1.default(val.value.toString()).div(1e18).toNumber()));
        return {
            price,
            lastUpdatedSlot,
            uiDeviation: stdDeviation,
            provider: OracleProvider.SwitchboardOnDemand,
        };
        // old block, we prefer above block since we want raw data, .result is often empty
        // const price = new Big(decodedPullFeed.result.value.toString()).div(1e18);
        // const lastUpdatedSlot = decodedPullFeed.result.slot.toNumber();
        // const stdDeviation = decodedPullFeed.result.stdDev.toNumber();
        // return { price, lastUpdatedSlot, uiDeviation: stdDeviation };
    }
    catch (e) {
        console.log(`Unable to parse Switchboard On-Demand Oracle V2: ${oracle}`, e);
        return {
            price: 0,
            lastUpdatedSlot: 0,
            uiDeviation: 0,
            provider: OracleProvider.SwitchboardOnDemand,
        };
    }
}
async function parseSwitchboardOracle(oracle, accountInfo, connection) {
    if (accountInfo.owner.equals(on_demand_1.SB_ON_DEMAND_PID)) {
        if (!exports.sbOnDemandProgram) {
            const options = anchor_1.AnchorProvider.defaultOptions();
            const provider = new anchor_1.AnchorProvider(connection, null, options);
            const idl = await switchboard_anchor_1.Program.fetchIdl(on_demand_1.SB_ON_DEMAND_PID, provider);
            exports.sbOnDemandProgram = new switchboard_anchor_1.Program(idl, provider);
        }
        return parseSwitchboardOnDemandOracle(exports.sbOnDemandProgram, accountInfo, oracle);
    }
    if (accountInfo.owner.equals(sbv2_lite_1.default.devnetPid)) {
        if (!sbv2DevnetProgram) {
            sbv2DevnetProgram = await sbv2_lite_1.default.loadDevnet(connection);
        }
        return parseSwitchboardOracleV2(sbv2DevnetProgram, accountInfo, oracle);
    }
    if (accountInfo.owner.equals(sbv2_lite_1.default.mainnetPid)) {
        if (!sbv2MainnetProgram) {
            sbv2MainnetProgram = await sbv2_lite_1.default.loadMainnet(connection);
        }
        return parseSwitchboardOracleV2(sbv2MainnetProgram, accountInfo, oracle);
    }
    if (accountInfo.owner.equals(SBV1_DEVNET_PID) ||
        accountInfo.owner.equals(SBV1_MAINNET_PID)) {
        return parseSwitchboardOracleV1(accountInfo);
    }
    throw new Error(`Should not be reached!`);
}
function isSwitchboardOracle(accountInfo) {
    if (accountInfo.owner.equals(SBV1_DEVNET_PID) ||
        accountInfo.owner.equals(SBV1_MAINNET_PID) ||
        accountInfo.owner.equals(sbv2_lite_1.default.devnetPid) ||
        accountInfo.owner.equals(sbv2_lite_1.default.mainnetPid) ||
        accountInfo.owner.equals(on_demand_1.SB_ON_DEMAND_PID)) {
        return true;
    }
    return false;
}
function isPythOracle(accountInfo) {
    if (accountInfo.owner.equals(constants_1.DEFAULT_RECEIVER_PROGRAM_ID)) {
        return true;
    }
    return accountInfo.data.readUInt32LE(0) === client_1.Magic;
}
function isOrcaOracle(accountInfo) {
    for (let i = 0; i < 8; i++) {
        if (accountInfo.data.at(i) !== ORCA_WHIRLPOOL_DISCRIMINATOR[i]) {
            return false;
        }
    }
    return (accountInfo.owner.equals(ORCA_MAINNET_PID) &&
        accountInfo.data.length == ORCA_WHIRLPOOL_LEN);
}
function isRaydiumOracle(accountInfo) {
    for (let i = 0; i < 8; i++) {
        if (accountInfo.data.at(i) !== RAYDIUM_POOL_DISCRIMINATOR[i]) {
            return false;
        }
    }
    return (accountInfo.owner.equals(RAYDIUM_MAINNET_PID) &&
        accountInfo.data.length == RAYDIUM_POOL_LEN);
}
function isClmmOracle(accountInfo) {
    return isOrcaOracle(accountInfo) || isRaydiumOracle(accountInfo);
}
function parsePythOracle(accountInfo, connection) {
    if (accountInfo.owner.equals(constants_1.DEFAULT_RECEIVER_PROGRAM_ID)) {
        if (!pythSolanaReceiverProgram) {
            const options = anchor_1.AnchorProvider.defaultOptions();
            const provider = new anchor_1.AnchorProvider(connection, null, options);
            pythSolanaReceiverProgram = new anchor_1.Program(pyth_solana_receiver_1.IDL, constants_1.DEFAULT_RECEIVER_PROGRAM_ID, provider);
        }
        const decoded = pythSolanaReceiverProgram.coder.accounts.decode('priceUpdateV2', accountInfo.data);
        return {
            price: (0, utils_1.toUiDecimals)(decoded.priceMessage.price.toNumber(), -decoded.priceMessage.exponent),
            publishedTime: decoded.priceMessage.publishTime.toNumber(),
            lastUpdatedSlot: decoded.postedSlot.toNumber(),
            uiDeviation: (0, utils_1.toUiDecimals)(decoded.priceMessage.conf.toNumber(), -decoded.priceMessage.exponent),
            provider: OracleProvider.PythV2,
        };
    }
    if (accountInfo.data.readUInt32LE(0) === client_1.Magic) {
        const priceData = (0, client_1.parsePriceData)(accountInfo.data);
        return {
            price: priceData.previousPrice,
            lastUpdatedSlot: parseInt(priceData.lastSlot.toString()),
            uiDeviation: priceData.previousConfidence,
            provider: OracleProvider.Pyth,
        };
    }
    throw new Error('Unknown Pyth oracle!');
}
function isOracleStaleOrUnconfident(nowSlot, maxStalenessSlots, oracleLastUpdatedSlot, deviation, confFilter, price) {
    if (maxStalenessSlots >= 0 &&
        oracleLastUpdatedSlot &&
        nowSlot > oracleLastUpdatedSlot + maxStalenessSlots) {
        return true;
    }
    if (deviation && deviation.gt(confFilter.mul(price))) {
        return true;
    }
    return false;
}
function deriveFallbackOracleQuoteKey(accountInfo) {
    if (isOrcaOracle(accountInfo)) {
        const tokenA = new web3_js_1.PublicKey(accountInfo.data.subarray(101, 133));
        const tokenB = new web3_js_1.PublicKey(accountInfo.data.subarray(181, 213));
        return clmmQuoteKey(tokenA, tokenB);
    }
    else if (isRaydiumOracle(accountInfo)) {
        const tokenA = new web3_js_1.PublicKey(accountInfo.data.subarray(73, 105));
        const tokenB = new web3_js_1.PublicKey(accountInfo.data.subarray(105, 137));
        return clmmQuoteKey(tokenA, tokenB);
    }
    else {
        return web3_js_1.PublicKey.default;
    }
}
function clmmQuoteKey(tokenA, tokenB) {
    if (tokenA.equals(exports.USDC_MINT_MAINNET) ||
        (tokenA.equals(exports.SOL_MINT_MAINNET) && !tokenB.equals(exports.USDC_MINT_MAINNET))) {
        return tokenA; // inverted
    }
    else {
        return tokenB;
    }
}
// Assumes oracles.length === fallbacks.length
async function createFallbackOracleMap(conn, oracles, fallbacks) {
    const map = new Map();
    const accounts = await conn.getMultipleAccountsInfo(fallbacks);
    for (let i = 0; i < oracles.length; i++) {
        if (accounts[i] === null) {
            map.set(oracles[i].toBase58(), [fallbacks[i], web3_js_1.PublicKey.default]);
        }
        else if (!isClmmOracle(accounts[i])) {
            map.set(oracles[i].toBase58(), [fallbacks[i], web3_js_1.PublicKey.default]);
        }
        else {
            const quoteKey = deriveFallbackOracleQuoteKey(accounts[i]);
            map.set(oracles[i].toBase58(), [fallbacks[i], quoteKey]);
        }
    }
    return map;
}
