import { Buffer } from "buffer"; import { BN, Program, web3 } from "@project-serum/anchor"; export const Magic = 0xa1b2c3d4; export const Version1 = 1; export const Version = Version1; export const PriceStatus = ["Unknown", "Trading", "Halted", "Auction"]; export const CorpAction = ["NoCorpAct"]; export const PriceType = ["Unknown", "Price", "TWAP", "Volatility"]; const empty32Buffer = Buffer.alloc(32); const PKorNull = (data: Buffer) => data.equals(empty32Buffer) ? null : new web3.PublicKey(data); interface ICreatePriceFeed { oracleProgram: Program; initPrice: number; confidence?: BN; expo?: number; } export const createPriceFeed = async ({ oracleProgram, initPrice, confidence, expo = -4, }: ICreatePriceFeed) => { const conf = confidence || new BN((initPrice / 10) * 10 ** -expo); const collateralTokenFeed = new web3.Account(); await oracleProgram.rpc.initialize( new BN(initPrice * 10 ** -expo), expo, conf, { accounts: { price: collateralTokenFeed.publicKey }, signers: [collateralTokenFeed], instructions: [ web3.SystemProgram.createAccount({ fromPubkey: oracleProgram.provider.wallet.publicKey, newAccountPubkey: collateralTokenFeed.publicKey, space: 3312, lamports: await oracleProgram.provider.connection.getMinimumBalanceForRentExemption( 3312 ), programId: oracleProgram.programId, }), ], } ); return collateralTokenFeed.publicKey; }; export const setFeedPrice = async ( oracleProgram: Program, newPrice: number, priceFeed: web3.PublicKey ) => { const info = await oracleProgram.provider.connection.getAccountInfo( priceFeed ); const data = parsePriceData(info.data); await oracleProgram.rpc.setPrice(new BN(newPrice * 10 ** -data.exponent), { accounts: { price: priceFeed }, }); }; export const getFeedData = async ( oracleProgram: Program, priceFeed: web3.PublicKey ) => { const info = await oracleProgram.provider.connection.getAccountInfo( priceFeed ); return parsePriceData(info.data); }; // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L758 const ERR_BUFFER_OUT_OF_BOUNDS = () => new Error("Attempt to access memory outside buffer bounds"); // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L968 const ERR_INVALID_ARG_TYPE = (name: string, expected: string, actual: any) => new Error( `The "${name}" argument must be of type ${expected}. Received ${actual}` ); // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L1262 const ERR_OUT_OF_RANGE = (str: string, range: string, received: number) => new Error( `The value of "${str} is out of range. It must be ${range}. Received ${received}` ); // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/validators.js#L127-L130 function validateNumber(value: any, name: string) { if (typeof value !== "number") throw ERR_INVALID_ARG_TYPE(name, "number", value); } // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/buffer.js#L68-L80 function boundsError(value: number, length: number) { if (Math.floor(value) !== value) { validateNumber(value, "offset"); throw ERR_OUT_OF_RANGE("offset", "an integer", value); } if (length < 0) throw ERR_BUFFER_OUT_OF_BOUNDS(); throw ERR_OUT_OF_RANGE("offset", `>= 0 and <= ${length}`, value); } export function readBigInt64LE(buffer: Buffer, offset = 0): bigint { validateNumber(offset, "offset"); const first = buffer[offset]; const last = buffer[offset + 7]; if (first === undefined || last === undefined) boundsError(offset, buffer.length - 8); const val = buffer[offset + 4] + buffer[offset + 5] * 2 ** 8 + buffer[offset + 6] * 2 ** 16 + (last << 24); // Overflow return ( (BigInt(val) << BigInt(32)) + BigInt( first + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + buffer[++offset] * 2 ** 24 ) ); } // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/buffer.js#L89-L107 export function readBigUInt64LE(buffer: Buffer, offset = 0): bigint { validateNumber(offset, "offset"); const first = buffer[offset]; const last = buffer[offset + 7]; if (first === undefined || last === undefined) boundsError(offset, buffer.length - 8); const lo = first + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + buffer[++offset] * 2 ** 24; const hi = buffer[++offset] + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + last * 2 ** 24; return BigInt(lo) + (BigInt(hi) << BigInt(32)); // tslint:disable-line:no-bitwise } export const parsePriceData = (data: Buffer) => { // Pyth magic number. const magic = data.readUInt32LE(0); // Program version. const version = data.readUInt32LE(4); // Account type. const type = data.readUInt32LE(8); // Price account size. const size = data.readUInt32LE(12); // Price or calculation type. const priceType = data.readUInt32LE(16); // Price exponent. const exponent = data.readInt32LE(20); // Number of component prices. const numComponentPrices = data.readUInt32LE(24); // unused // const unused = accountInfo.data.readUInt32LE(28) // Currently accumulating price slot. const currentSlot = readBigUInt64LE(data, 32); // Valid on-chain slot of aggregate price. const validSlot = readBigUInt64LE(data, 40); // Time-weighted average price. const twapComponent = readBigInt64LE(data, 48); const twap = Number(twapComponent) * 10 ** exponent; // Annualized price volatility. const avolComponent = readBigUInt64LE(data, 56); const avol = Number(avolComponent) * 10 ** exponent; // Space for future derived values. const drv0Component = readBigInt64LE(data, 64); const drv0 = Number(drv0Component) * 10 ** exponent; const drv1Component = readBigInt64LE(data, 72); const drv1 = Number(drv1Component) * 10 ** exponent; const drv2Component = readBigInt64LE(data, 80); const drv2 = Number(drv2Component) * 10 ** exponent; const drv3Component = readBigInt64LE(data, 88); const drv3 = Number(drv3Component) * 10 ** exponent; const drv4Component = readBigInt64LE(data, 96); const drv4 = Number(drv4Component) * 10 ** exponent; const drv5Component = readBigInt64LE(data, 104); const drv5 = Number(drv5Component) * 10 ** exponent; // Product id / reference account. const productAccountKey = new web3.PublicKey(data.slice(112, 144)); // Next price account in list. const nextPriceAccountKey = PKorNull(data.slice(144, 176)); // Aggregate price updater. const aggregatePriceUpdaterAccountKey = new web3.PublicKey( data.slice(176, 208) ); const aggregatePriceInfo = parsePriceInfo(data.slice(208, 240), exponent); // Price components - up to 32. const priceComponents = []; let offset = 240; let shouldContinue = true; while (offset < data.length && shouldContinue) { const publisher = PKorNull(data.slice(offset, offset + 32)); offset += 32; if (publisher) { const aggregate = parsePriceInfo( data.slice(offset, offset + 32), exponent ); offset += 32; const latest = parsePriceInfo(data.slice(offset, offset + 32), exponent); offset += 32; priceComponents.push({ publisher, aggregate, latest }); } else { shouldContinue = false; } } return { magic, version, type, size, priceType, exponent, numComponentPrices, currentSlot, validSlot, twapComponent, twap, avolComponent, avol, drv0Component, drv0, drv1Component, drv1, drv2Component, drv2, drv3Component, drv3, drv4Component, drv4, drv5Component, drv5, productAccountKey, nextPriceAccountKey, aggregatePriceUpdaterAccountKey, ...aggregatePriceInfo, priceComponents, }; }; interface ProductAttributes { [index: string]: string; } export const parseProductData = (data: Buffer) => { // Pyth magic number. const magic = data.readUInt32LE(0); // Program version. const version = data.readUInt32LE(4); // Account type. const type = data.readUInt32LE(8); // Price account size. const size = data.readUInt32LE(12); // First price account in list. const priceAccountBytes = data.slice(16, 48); const priceAccountKey = new web3.PublicKey(priceAccountBytes); const product: ProductAttributes = {}; let idx = 48; while (idx < data.length) { const keyLength = data[idx]; idx++; if (keyLength) { const key = data.slice(idx, idx + keyLength).toString(); idx += keyLength; const valueLength = data[idx]; idx++; const value = data.slice(idx, idx + valueLength).toString(); idx += valueLength; product[key] = value; } } return { magic, version, type, size, priceAccountKey, product }; }; const parsePriceInfo = (data: Buffer, exponent: number) => { // Aggregate price. const priceComponent = data.readBigUInt64LE(0); const price = Number(priceComponent) * 10 ** exponent; // Aggregate confidence. const confidenceComponent = data.readBigUInt64LE(8); const confidence = Number(confidenceComponent) * 10 ** exponent; // Aggregate status. const status = data.readUInt32LE(16); // Aggregate corporate action. const corporateAction = data.readUInt32LE(20); // Aggregate publish slot. const publishSlot = data.readBigUInt64LE(24); return { priceComponent, price, confidenceComponent, confidence, status, corporateAction, publishSlot, }; };