|
@@ -1,21 +1,22 @@
|
|
|
-import { Buffer } from 'buffer'
|
|
|
-import { BN, Program, web3 } from '@project-serum/anchor'
|
|
|
+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']
|
|
|
+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))
|
|
|
+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
|
|
|
+ oracleProgram: Program;
|
|
|
+ initPrice: number;
|
|
|
+ confidence?: BN;
|
|
|
+ expo?: number;
|
|
|
}
|
|
|
export const createPriceFeed = async ({
|
|
|
oracleProgram,
|
|
@@ -23,160 +24,200 @@ export const createPriceFeed = async ({
|
|
|
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
|
|
|
-}
|
|
|
+ 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)
|
|
|
+ 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)
|
|
|
-}
|
|
|
+ });
|
|
|
+};
|
|
|
+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')
|
|
|
+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}`)
|
|
|
+ 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}`)
|
|
|
+ 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)
|
|
|
+ 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)
|
|
|
+ validateNumber(value, "offset");
|
|
|
+ throw ERR_OUT_OF_RANGE("offset", "an integer", value);
|
|
|
}
|
|
|
|
|
|
- if (length < 0) throw ERR_BUFFER_OUT_OF_BOUNDS()
|
|
|
+ if (length < 0) throw ERR_BUFFER_OUT_OF_BOUNDS();
|
|
|
|
|
|
- throw ERR_OUT_OF_RANGE('offset', `>= 0 and <= ${length}`, value)
|
|
|
+ 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)
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ 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)
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ 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)
|
|
|
+ const magic = data.readUInt32LE(0);
|
|
|
// Program version.
|
|
|
- const version = data.readUInt32LE(4)
|
|
|
+ const version = data.readUInt32LE(4);
|
|
|
// Account type.
|
|
|
- const type = data.readUInt32LE(8)
|
|
|
+ const type = data.readUInt32LE(8);
|
|
|
// Price account size.
|
|
|
- const size = data.readUInt32LE(12)
|
|
|
+ const size = data.readUInt32LE(12);
|
|
|
// Price or calculation type.
|
|
|
- const priceType = data.readUInt32LE(16)
|
|
|
+ const priceType = data.readUInt32LE(16);
|
|
|
// Price exponent.
|
|
|
- const exponent = data.readInt32LE(20)
|
|
|
+ const exponent = data.readInt32LE(20);
|
|
|
// Number of component prices.
|
|
|
- const numComponentPrices = data.readUInt32LE(24)
|
|
|
+ const numComponentPrices = data.readUInt32LE(24);
|
|
|
// unused
|
|
|
// const unused = accountInfo.data.readUInt32LE(28)
|
|
|
// Currently accumulating price slot.
|
|
|
- const currentSlot = readBigUInt64LE(data, 32)
|
|
|
+ const currentSlot = readBigUInt64LE(data, 32);
|
|
|
// Valid on-chain slot of aggregate price.
|
|
|
- const validSlot = readBigUInt64LE(data, 40)
|
|
|
+ const validSlot = readBigUInt64LE(data, 40);
|
|
|
// Time-weighted average price.
|
|
|
- const twapComponent = readBigInt64LE(data, 48)
|
|
|
- const twap = Number(twapComponent) * 10 ** exponent
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ 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))
|
|
|
+ const productAccountKey = new web3.PublicKey(data.slice(112, 144));
|
|
|
// Next price account in list.
|
|
|
- const nextPriceAccountKey = PKorNull(data.slice(144, 176))
|
|
|
+ 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)
|
|
|
+ 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
|
|
|
+ const priceComponents = [];
|
|
|
+ let offset = 240;
|
|
|
+ let shouldContinue = true;
|
|
|
while (offset < data.length && shouldContinue) {
|
|
|
- const publisher = PKorNull(data.slice(offset, offset + 32))
|
|
|
- offset += 32
|
|
|
+ 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 })
|
|
|
+ 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
|
|
|
+ shouldContinue = false;
|
|
|
}
|
|
|
}
|
|
|
return {
|
|
@@ -210,56 +251,56 @@ export const parsePriceData = (data: Buffer) => {
|
|
|
aggregatePriceUpdaterAccountKey,
|
|
|
...aggregatePriceInfo,
|
|
|
priceComponents,
|
|
|
- }
|
|
|
-}
|
|
|
+ };
|
|
|
+};
|
|
|
|
|
|
interface ProductAttributes {
|
|
|
- [index: string]: string
|
|
|
+ [index: string]: string;
|
|
|
}
|
|
|
|
|
|
export const parseProductData = (data: Buffer) => {
|
|
|
// Pyth magic number.
|
|
|
- const magic = data.readUInt32LE(0)
|
|
|
+ const magic = data.readUInt32LE(0);
|
|
|
// Program version.
|
|
|
- const version = data.readUInt32LE(4)
|
|
|
+ const version = data.readUInt32LE(4);
|
|
|
// Account type.
|
|
|
- const type = data.readUInt32LE(8)
|
|
|
+ const type = data.readUInt32LE(8);
|
|
|
// Price account size.
|
|
|
- const size = data.readUInt32LE(12)
|
|
|
+ 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
|
|
|
+ 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++
|
|
|
+ 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
|
|
|
+ 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 }
|
|
|
-}
|
|
|
+ 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
|
|
|
+ const priceComponent = data.readBigUInt64LE(0);
|
|
|
+ const price = Number(priceComponent) * 10 ** exponent;
|
|
|
// Aggregate confidence.
|
|
|
- const confidenceComponent = data.readBigUInt64LE(8)
|
|
|
- const confidence = Number(confidenceComponent) * 10 ** exponent
|
|
|
+ const confidenceComponent = data.readBigUInt64LE(8);
|
|
|
+ const confidence = Number(confidenceComponent) * 10 ** exponent;
|
|
|
// Aggregate status.
|
|
|
- const status = data.readUInt32LE(16)
|
|
|
+ const status = data.readUInt32LE(16);
|
|
|
// Aggregate corporate action.
|
|
|
- const corporateAction = data.readUInt32LE(20)
|
|
|
+ const corporateAction = data.readUInt32LE(20);
|
|
|
// Aggregate publish slot.
|
|
|
- const publishSlot = data.readBigUInt64LE(24)
|
|
|
+ const publishSlot = data.readBigUInt64LE(24);
|
|
|
return {
|
|
|
priceComponent,
|
|
|
price,
|
|
@@ -268,5 +309,5 @@ const parsePriceInfo = (data: Buffer, exponent: number) => {
|
|
|
status,
|
|
|
corporateAction,
|
|
|
publishSlot,
|
|
|
- }
|
|
|
-}
|
|
|
+ };
|
|
|
+};
|