UNPKG

@pythnetwork/client

Version:

Client for consuming Pyth price data

319 lines (318 loc) 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPythProgramKeyForCluster = exports.getPythClusterApiUrl = exports.pythOracleProgram = exports.pythOracleCoder = exports.pythIdl = exports.PythHttpClient = exports.PythConnection = exports.parsePermissionData = exports.parsePriceData = exports.parseProductData = exports.parseMappingData = exports.parseBaseData = exports.AccountType = exports.DeriveType = exports.PriceType = exports.CorpAction = exports.PriceStatus = exports.MAX_SLOT_DIFFERENCE = exports.Version = exports.Version2 = exports.Magic = void 0; var web3_js_1 = require("@solana/web3.js"); var buffer_1 = require("buffer"); var readBig_1 = require("./readBig"); /** Constants. This section must be kept in sync with the on-chain program. */ exports.Magic = 0xa1b2c3d4; exports.Version2 = 2; exports.Version = exports.Version2; /** Number of slots that can pass before a publisher's price is no longer included in the aggregate. */ exports.MAX_SLOT_DIFFERENCE = 25; var PriceStatus; (function (PriceStatus) { PriceStatus[PriceStatus["Unknown"] = 0] = "Unknown"; PriceStatus[PriceStatus["Trading"] = 1] = "Trading"; PriceStatus[PriceStatus["Halted"] = 2] = "Halted"; PriceStatus[PriceStatus["Auction"] = 3] = "Auction"; PriceStatus[PriceStatus["Ignored"] = 4] = "Ignored"; })(PriceStatus = exports.PriceStatus || (exports.PriceStatus = {})); var CorpAction; (function (CorpAction) { CorpAction[CorpAction["NoCorpAct"] = 0] = "NoCorpAct"; })(CorpAction = exports.CorpAction || (exports.CorpAction = {})); var PriceType; (function (PriceType) { PriceType[PriceType["Unknown"] = 0] = "Unknown"; PriceType[PriceType["Price"] = 1] = "Price"; })(PriceType = exports.PriceType || (exports.PriceType = {})); var DeriveType; (function (DeriveType) { DeriveType[DeriveType["Unknown"] = 0] = "Unknown"; DeriveType[DeriveType["Volatility"] = 1] = "Volatility"; })(DeriveType = exports.DeriveType || (exports.DeriveType = {})); var AccountType; (function (AccountType) { AccountType[AccountType["Unknown"] = 0] = "Unknown"; AccountType[AccountType["Mapping"] = 1] = "Mapping"; AccountType[AccountType["Product"] = 2] = "Product"; AccountType[AccountType["Price"] = 3] = "Price"; AccountType[AccountType["Test"] = 4] = "Test"; AccountType[AccountType["Permission"] = 5] = "Permission"; })(AccountType = exports.AccountType || (exports.AccountType = {})); var empty32Buffer = buffer_1.Buffer.alloc(32); var PKorNull = function (data) { return (data.equals(empty32Buffer) ? null : new web3_js_1.PublicKey(data)); }; /** Parse data as a generic Pyth account. Use this method if you don't know the account type. */ function parseBaseData(data) { // data is too short to have the magic number. if (data.byteLength < 4) { return undefined; } var magic = data.readUInt32LE(0); if (magic === exports.Magic) { // program version var version = data.readUInt32LE(4); // account type var type = data.readUInt32LE(8); // account used size var size = data.readUInt32LE(12); return { magic: magic, version: version, type: type, size: size }; } else { return undefined; } } exports.parseBaseData = parseBaseData; var parseMappingData = function (data) { // pyth magic number var magic = data.readUInt32LE(0); // program version var version = data.readUInt32LE(4); // account type var type = data.readUInt32LE(8); // account used size var size = data.readUInt32LE(12); // number of product accounts var numProducts = data.readUInt32LE(16); // unused // const unused = accountInfo.data.readUInt32LE(20) // next mapping account (if any) var nextMappingAccount = PKorNull(data.slice(24, 56)); // read each symbol account var offset = 56; var productAccountKeys = []; for (var i = 0; i < numProducts; i++) { var productAccountBytes = data.slice(offset, offset + 32); var productAccountKey = new web3_js_1.PublicKey(productAccountBytes); offset += 32; productAccountKeys.push(productAccountKey); } return { magic: magic, version: version, type: type, size: size, nextMappingAccount: nextMappingAccount, productAccountKeys: productAccountKeys, }; }; exports.parseMappingData = parseMappingData; var parseProductData = function (data) { // pyth magic number var magic = data.readUInt32LE(0); // program version var version = data.readUInt32LE(4); // account type var type = data.readUInt32LE(8); // price account size var size = data.readUInt32LE(12); // first price account in list var priceAccountBytes = data.slice(16, 48); var priceAccountKey = PKorNull(priceAccountBytes); var product = {}; if (priceAccountKey) product.price_account = priceAccountKey.toBase58(); var idx = 48; while (idx < size) { var keyLength = data[idx]; idx++; if (keyLength) { var key = data.slice(idx, idx + keyLength).toString(); idx += keyLength; var valueLength = data[idx]; idx++; var value = data.slice(idx, idx + valueLength).toString(); idx += valueLength; product[key] = value; } } return { magic: magic, version: version, type: type, size: size, priceAccountKey: priceAccountKey, product: product }; }; exports.parseProductData = parseProductData; var parseEma = function (data, exponent) { // current value of ema var valueComponent = readBig_1.readBigInt64LE(data, 0); var value = Number(valueComponent) * Math.pow(10, exponent); // numerator state for next update var numerator = readBig_1.readBigInt64LE(data, 8); // denominator state for next update var denominator = readBig_1.readBigInt64LE(data, 16); return { valueComponent: valueComponent, value: value, numerator: numerator, denominator: denominator }; }; var parsePriceInfo = function (data, exponent) { // aggregate price var priceComponent = readBig_1.readBigInt64LE(data, 0); var price = Number(priceComponent) * Math.pow(10, exponent); // aggregate confidence var confidenceComponent = readBig_1.readBigUInt64LE(data, 8); var confidence = Number(confidenceComponent) * Math.pow(10, exponent); // aggregate status var status = data.readUInt32LE(16); // aggregate corporate action var corporateAction = data.readUInt32LE(20); // aggregate publish slot. It is converted to number to be consistent with Solana's library interface (Slot there is number) var publishSlot = Number(readBig_1.readBigUInt64LE(data, 24)); return { priceComponent: priceComponent, price: price, confidenceComponent: confidenceComponent, confidence: confidence, status: status, corporateAction: corporateAction, publishSlot: publishSlot, }; }; // Provide currentSlot when available to allow status to consider the case when price goes stale. It is optional because // it requires an extra request to get it when it is not available which is not always efficient. var parsePriceData = function (data, currentSlot) { // pyth magic number var magic = data.readUInt32LE(0); // program version var version = data.readUInt32LE(4); // account type var type = data.readUInt32LE(8); // price account size var size = data.readUInt32LE(12); // price or calculation type var priceType = data.readUInt32LE(16); // price exponent var exponent = data.readInt32LE(20); // number of component prices var numComponentPrices = data.readUInt32LE(24); // number of quoters that make up aggregate var numQuoters = data.readUInt32LE(28); // slot of last valid (not unknown) aggregate price var lastSlot = readBig_1.readBigUInt64LE(data, 32); // valid on-chain slot of aggregate price var validSlot = readBig_1.readBigUInt64LE(data, 40); // exponential moving average price var emaPrice = parseEma(data.slice(48, 72), exponent); // exponential moving average confidence interval var emaConfidence = parseEma(data.slice(72, 96), exponent); // timestamp of the current price var timestamp = readBig_1.readBigInt64LE(data, 96); // minimum number of publishers for status to be TRADING var minPublishers = data.readUInt8(104); // message sent flag var messageSent = data.readUInt8(105); // configurable max latency in slots between send and receive var maxLatency = data.readUInt8(106); // Various flags (used for operations) var flagBits = data.readInt8(107); /* tslint:disable:no-bitwise */ var flags = { accumulatorV2: (flagBits & (1 << 0)) !== 0, messageBufferCleared: (flagBits & (1 << 1)) !== 0, }; /* tslint:enable:no-bitwise */ // Globally immutable unique price feed index used for publishing. var feedIndex = data.readInt32LE(108); // product id / reference account var productAccountKey = new web3_js_1.PublicKey(data.slice(112, 144)); // next price account in list var nextPriceAccountKey = PKorNull(data.slice(144, 176)); // valid slot of previous update var previousSlot = readBig_1.readBigUInt64LE(data, 176); // aggregate price of previous update var previousPriceComponent = readBig_1.readBigInt64LE(data, 184); var previousPrice = Number(previousPriceComponent) * Math.pow(10, exponent); // confidence interval of previous update var previousConfidenceComponent = readBig_1.readBigUInt64LE(data, 192); var previousConfidence = Number(previousConfidenceComponent) * Math.pow(10, exponent); // space for future derived values var previousTimestamp = readBig_1.readBigInt64LE(data, 200); var aggregate = parsePriceInfo(data.slice(208, 240), exponent); var status = aggregate.status; if (currentSlot && status === PriceStatus.Trading) { if (currentSlot - aggregate.publishSlot > exports.MAX_SLOT_DIFFERENCE) { status = PriceStatus.Unknown; } } var price; var confidence; if (status === PriceStatus.Trading) { price = aggregate.price; confidence = aggregate.confidence; } // price components - up to 32 var priceComponents = []; var offset = 240; while (priceComponents.length < numComponentPrices) { var publisher = new web3_js_1.PublicKey(data.slice(offset, offset + 32)); offset += 32; var componentAggregate = parsePriceInfo(data.slice(offset, offset + 32), exponent); offset += 32; var latest = parsePriceInfo(data.slice(offset, offset + 32), exponent); offset += 32; priceComponents.push({ publisher: publisher, aggregate: componentAggregate, latest: latest }); } return { magic: magic, version: version, type: type, size: size, priceType: priceType, exponent: exponent, numComponentPrices: numComponentPrices, numQuoters: numQuoters, lastSlot: lastSlot, validSlot: validSlot, emaPrice: emaPrice, emaConfidence: emaConfidence, timestamp: timestamp, minPublishers: minPublishers, messageSent: messageSent, maxLatency: maxLatency, flags: flags, feedIndex: feedIndex, productAccountKey: productAccountKey, nextPriceAccountKey: nextPriceAccountKey, previousSlot: previousSlot, previousPriceComponent: previousPriceComponent, previousPrice: previousPrice, previousConfidenceComponent: previousConfidenceComponent, previousConfidence: previousConfidence, previousTimestamp: previousTimestamp, aggregate: aggregate, priceComponents: priceComponents, price: price, confidence: confidence, status: status, }; }; exports.parsePriceData = parsePriceData; var parsePermissionData = function (data) { // pyth magic number var magic = data.readUInt32LE(0); // program version var version = data.readUInt32LE(4); // account type var type = data.readUInt32LE(8); // price account size var size = data.readUInt32LE(12); var masterAuthority = new web3_js_1.PublicKey(data.slice(16, 48)); var dataCurationAuthority = new web3_js_1.PublicKey(data.slice(48, 80)); var securityAuthority = new web3_js_1.PublicKey(data.slice(80, 112)); return { magic: magic, version: version, type: type, size: size, masterAuthority: masterAuthority, dataCurationAuthority: dataCurationAuthority, securityAuthority: securityAuthority, }; }; exports.parsePermissionData = parsePermissionData; var PythConnection_1 = require("./PythConnection"); Object.defineProperty(exports, "PythConnection", { enumerable: true, get: function () { return PythConnection_1.PythConnection; } }); var PythHttpClient_1 = require("./PythHttpClient"); Object.defineProperty(exports, "PythHttpClient", { enumerable: true, get: function () { return PythHttpClient_1.PythHttpClient; } }); var anchor_1 = require("./anchor"); Object.defineProperty(exports, "pythIdl", { enumerable: true, get: function () { return anchor_1.pythIdl; } }); Object.defineProperty(exports, "pythOracleCoder", { enumerable: true, get: function () { return anchor_1.pythOracleCoder; } }); Object.defineProperty(exports, "pythOracleProgram", { enumerable: true, get: function () { return anchor_1.pythOracleProgram; } }); var cluster_1 = require("./cluster"); Object.defineProperty(exports, "getPythClusterApiUrl", { enumerable: true, get: function () { return cluster_1.getPythClusterApiUrl; } }); Object.defineProperty(exports, "getPythProgramKeyForCluster", { enumerable: true, get: function () { return cluster_1.getPythProgramKeyForCluster; } });