123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- import { Buffer } from "buffer";
- import { AnchorProvider, BN, Program, web3 } from "@coral-xyz/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;
- provider: AnchorProvider;
- }
- export const createPriceFeed = async ({
- oracleProgram,
- initPrice,
- confidence,
- expo = -4,
- provider,
- }: 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: provider.wallet.publicKey,
- newAccountPubkey: collateralTokenFeed.publicKey,
- space: 3312,
- lamports: await 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,
- };
- };
|