oracleUtils.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { Buffer } from 'buffer'
  2. import { BN, Program, web3 } from '@project-serum/anchor'
  3. export const Magic = 0xa1b2c3d4
  4. export const Version1 = 1
  5. export const Version = Version1
  6. export const PriceStatus = ['Unknown', 'Trading', 'Halted', 'Auction']
  7. export const CorpAction = ['NoCorpAct']
  8. export const PriceType = ['Unknown', 'Price', 'TWAP', 'Volatility']
  9. const empty32Buffer = Buffer.alloc(32)
  10. const PKorNull = (data: Buffer) => (data.equals(empty32Buffer) ? null : new web3.PublicKey(data))
  11. interface ICreatePriceFeed {
  12. oracleProgram: Program
  13. initPrice: number
  14. confidence?: BN
  15. expo?: number
  16. }
  17. export const createPriceFeed = async ({
  18. oracleProgram,
  19. initPrice,
  20. confidence,
  21. expo = -4,
  22. }: ICreatePriceFeed) => {
  23. const conf = confidence || new BN((initPrice / 10) * 10 ** -expo)
  24. const collateralTokenFeed = new web3.Account()
  25. await oracleProgram.rpc.initialize(new BN(initPrice * 10 ** -expo), expo, conf, {
  26. accounts: { price: collateralTokenFeed.publicKey },
  27. signers: [collateralTokenFeed],
  28. instructions: [
  29. web3.SystemProgram.createAccount({
  30. fromPubkey: oracleProgram.provider.wallet.publicKey,
  31. newAccountPubkey: collateralTokenFeed.publicKey,
  32. space: 1712,
  33. lamports: await oracleProgram.provider.connection.getMinimumBalanceForRentExemption(1712),
  34. programId: oracleProgram.programId,
  35. }),
  36. ],
  37. })
  38. return collateralTokenFeed.publicKey
  39. }
  40. export const setFeedPrice = async (
  41. oracleProgram: Program,
  42. newPrice: number,
  43. priceFeed: web3.PublicKey
  44. ) => {
  45. const info = await oracleProgram.provider.connection.getAccountInfo(priceFeed)
  46. const data = parsePriceData(info.data)
  47. await oracleProgram.rpc.setPrice(new BN(newPrice * 10 ** -data.exponent), {
  48. accounts: { price: priceFeed },
  49. })
  50. }
  51. export const getFeedData = async (oracleProgram: Program, priceFeed: web3.PublicKey) => {
  52. const info = await oracleProgram.provider.connection.getAccountInfo(priceFeed)
  53. return parsePriceData(info.data)
  54. }
  55. export const parseMappingData = (data: Buffer) => {
  56. // Pyth magic number.
  57. const magic = data.readUInt32LE(0)
  58. // Program version.
  59. const version = data.readUInt32LE(4)
  60. // Account type.
  61. const type = data.readUInt32LE(8)
  62. // Account used size.
  63. const size = data.readUInt32LE(12)
  64. // Number of product accounts.
  65. const numProducts = data.readUInt32LE(16)
  66. // Unused.
  67. // const unused = accountInfo.data.readUInt32LE(20)
  68. // TODO: check and use this.
  69. // Next mapping account (if any).
  70. const nextMappingAccount = PKorNull(data.slice(24, 56))
  71. // Read each symbol account.
  72. let offset = 56
  73. const productAccountKeys = []
  74. for (let i = 0; i < numProducts; i++) {
  75. const productAccountBytes = data.slice(offset, offset + 32)
  76. const productAccountKey = new web3.PublicKey(productAccountBytes)
  77. offset += 32
  78. productAccountKeys.push(productAccountKey)
  79. }
  80. return {
  81. magic,
  82. version,
  83. type,
  84. size,
  85. nextMappingAccount,
  86. productAccountKeys,
  87. }
  88. }
  89. interface ProductAttributes {
  90. [index: string]: string
  91. }
  92. export const parseProductData = (data: Buffer) => {
  93. // Pyth magic number.
  94. const magic = data.readUInt32LE(0)
  95. // Program version.
  96. const version = data.readUInt32LE(4)
  97. // Account type.
  98. const type = data.readUInt32LE(8)
  99. // Price account size.
  100. const size = data.readUInt32LE(12)
  101. // First price account in list.
  102. const priceAccountBytes = data.slice(16, 48)
  103. const priceAccountKey = new web3.PublicKey(priceAccountBytes)
  104. const product: ProductAttributes = {}
  105. let idx = 48
  106. while (idx < data.length) {
  107. const keyLength = data[idx]
  108. idx++
  109. if (keyLength) {
  110. const key = data.slice(idx, idx + keyLength).toString()
  111. idx += keyLength
  112. const valueLength = data[idx]
  113. idx++
  114. const value = data.slice(idx, idx + valueLength).toString()
  115. idx += valueLength
  116. product[key] = value
  117. }
  118. }
  119. return { magic, version, type, size, priceAccountKey, product }
  120. }
  121. const parsePriceInfo = (data: Buffer, exponent: number) => {
  122. // Aggregate price.
  123. const priceComponent = data.readBigUInt64LE(0)
  124. const price = Number(priceComponent) * 10 ** exponent
  125. // Aggregate confidence.
  126. const confidenceComponent = data.readBigUInt64LE(8)
  127. const confidence = Number(confidenceComponent) * 10 ** exponent
  128. // Aggregate status.
  129. const status = data.readUInt32LE(16)
  130. // Aggregate corporate action.
  131. const corporateAction = data.readUInt32LE(20)
  132. // Aggregate publish slot.
  133. const publishSlot = data.readBigUInt64LE(24)
  134. return {
  135. priceComponent,
  136. price,
  137. confidenceComponent,
  138. confidence,
  139. status,
  140. corporateAction,
  141. publishSlot,
  142. }
  143. }
  144. export const parsePriceData = (data: Buffer) => {
  145. // Pyth magic number.
  146. const magic = data.readUInt32LE(0)
  147. // Program version.
  148. const version = data.readUInt32LE(4)
  149. // Account type.
  150. const type = data.readUInt32LE(8)
  151. // Price account size.
  152. const size = data.readUInt32LE(12)
  153. // Price or calculation type.
  154. const priceType = data.readUInt32LE(16)
  155. // Price exponent.
  156. const exponent = data.readInt32LE(20)
  157. // Number of component prices.
  158. const numComponentPrices = data.readUInt32LE(24)
  159. // Unused.
  160. // const unused = accountInfo.data.readUInt32LE(28)
  161. // Currently accumulating price slot.
  162. const currentSlot = data.readBigUInt64LE(32)
  163. // Valid on-chain slot of aggregate price.
  164. const validSlot = data.readBigUInt64LE(40)
  165. // Product id / reference account.
  166. const productAccountKey = new web3.PublicKey(data.slice(48, 80))
  167. // Next price account in list.
  168. const nextPriceAccountKey = new web3.PublicKey(data.slice(80, 112))
  169. // Aggregate price updater.
  170. const aggregatePriceUpdaterAccountKey = new web3.PublicKey(data.slice(112, 144))
  171. const aggregatePriceInfo = parsePriceInfo(data.slice(144, 176), exponent)
  172. // Urice components - up to 16.
  173. const priceComponents = []
  174. let offset = 176
  175. let shouldContinue = true
  176. while (offset < data.length && shouldContinue) {
  177. const publisher = PKorNull(data.slice(offset, offset + 32))
  178. offset += 32
  179. if (publisher) {
  180. const aggregate = parsePriceInfo(data.slice(offset, offset + 32), exponent)
  181. offset += 32
  182. const latest = parsePriceInfo(data.slice(offset, offset + 32), exponent)
  183. offset += 32
  184. priceComponents.push({ publisher, aggregate, latest })
  185. } else {
  186. shouldContinue = false
  187. }
  188. }
  189. return {
  190. magic,
  191. version,
  192. type,
  193. size,
  194. priceType,
  195. exponent,
  196. numComponentPrices,
  197. currentSlot,
  198. validSlot,
  199. productAccountKey,
  200. nextPriceAccountKey,
  201. aggregatePriceUpdaterAccountKey,
  202. ...aggregatePriceInfo,
  203. priceComponents,
  204. }
  205. }