Skip to content

Commit 1b9d17c

Browse files
authored
feat: add InitPriceFeedIndex method IDL (#80)
* feat: add InitPriceFeedIndex method IDL * fix: update tests to use pythnet/pythtest * fix: address review comments * feat: add tests for this instruction and setMaxLatency * refactor: format * fix: remove permissions account from the test as its pda * feat: add account parsing * fix: address lint issues * fix: update oracle version in idl.json
1 parent 8a9c94b commit 1b9d17c

13 files changed

+175
-38
lines changed

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/client",
3-
"version": "2.21.1",
3+
"version": "2.22.0",
44
"description": "Client for consuming Pyth price data",
55
"homepage": "https://pyth.network",
66
"main": "lib/index.js",

src/__tests__/Anchor.test.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { AnchorProvider, Wallet } from '@coral-xyz/anchor'
22
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
33
import { BN } from 'bn.js'
4+
import { getPythClusterApiUrl } from '../cluster'
45
import { getPythProgramKeyForCluster, pythOracleProgram, pythOracleCoder } from '../index'
56

67
test('Anchor', (done) => {
78
jest.setTimeout(60000)
89
const provider = new AnchorProvider(
9-
new Connection('https://api.mainnet-beta.solana.com'),
10+
new Connection(getPythClusterApiUrl('pythnet')),
1011
new Wallet(new Keypair()),
1112
AnchorProvider.defaultOptions(),
1213
)
13-
const pythOracle = pythOracleProgram(getPythProgramKeyForCluster('mainnet-beta'), provider)
14+
const pythOracle = pythOracleProgram(getPythProgramKeyForCluster('pythnet'), provider)
1415
pythOracle.methods
1516
.initMapping()
1617
.accounts({ fundingAccount: PublicKey.unique(), freshMappingAccount: PublicKey.unique() })
@@ -202,5 +203,30 @@ test('Anchor', (done) => {
202203
expect(decoded?.data.securityAuthority.equals(new PublicKey(8))).toBeTruthy()
203204
})
204205

206+
pythOracle.methods
207+
.setMaxLatency(1, [0, 0, 0])
208+
.accounts({ fundingAccount: PublicKey.unique(), priceAccount: PublicKey.unique() })
209+
.instruction()
210+
.then((instruction) => {
211+
expect(instruction.data).toStrictEqual(Buffer.from([2, 0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0]))
212+
const decoded = pythOracleCoder().instruction.decode(instruction.data)
213+
expect(decoded?.name).toBe('setMaxLatency')
214+
expect(decoded?.data.maxLatency === 1).toBeTruthy()
215+
})
216+
217+
pythOracle.methods
218+
.initPriceFeedIndex()
219+
.accounts({
220+
fundingAccount: PublicKey.unique(),
221+
priceAccount: PublicKey.unique(),
222+
})
223+
.instruction()
224+
.then((instruction) => {
225+
expect(instruction.data).toStrictEqual(Buffer.from([2, 0, 0, 0, 19, 0, 0, 0]))
226+
const decoded = pythOracleCoder().instruction.decode(instruction.data)
227+
expect(decoded?.name).toBe('initPriceFeedIndex')
228+
expect(decoded?.data).toStrictEqual({})
229+
})
230+
205231
done()
206232
})

src/__tests__/Example.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'
2-
import { parseMappingData, parsePriceData, parseProductData } from '../index'
1+
import { Connection, PublicKey } from '@solana/web3.js'
2+
import { getPythClusterApiUrl, parseMappingData, parsePriceData, parseProductData } from '../index'
33

4-
const SOLANA_CLUSTER_URL = clusterApiUrl('devnet')
4+
const SOLANA_CLUSTER_URL = getPythClusterApiUrl('pythtest-crosschain')
55
const ORACLE_MAPPING_PUBLIC_KEY = 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2'
66

77
test('Mapping', (done) => {

src/__tests__/Mapping.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'
2-
import { parseMappingData, Magic, Version } from '../index'
1+
import { Connection, PublicKey } from '@solana/web3.js'
2+
import { parseMappingData, Magic, Version, getPythClusterApiUrl } from '../index'
33

44
test('Mapping', (done) => {
55
jest.setTimeout(60000)
6-
const url = clusterApiUrl('devnet')
6+
const url = getPythClusterApiUrl('pythtest-crosschain')
77
const oraclePublicKey = 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2'
88
const connection = new Connection(url)
99
const publicKey = new PublicKey(oraclePublicKey)

src/__tests__/Price.test.ts

+36-2
Large diffs are not rendered by default.

src/__tests__/Product.ETH.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'
2-
import { Magic, parseProductData, Version } from '../index'
1+
import { Connection, PublicKey } from '@solana/web3.js'
2+
import { getPythClusterApiUrl, Magic, parseProductData, Version } from '../index'
33

44
test('Product', (done) => {
55
jest.setTimeout(60000)
6-
const url = clusterApiUrl('devnet')
6+
const url = getPythClusterApiUrl('pythtest-crosschain')
77
const ethProductKey = '2ciUuGZiee5macAMeQ7bHGTJtwcYTgnt6jdmQnnKZrfu'
88
const connection = new Connection(url)
99
const publicKey = new PublicKey(ethProductKey)

src/__tests__/Product.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'
1+
import { Connection, PublicKey } from '@solana/web3.js'
2+
import { getPythClusterApiUrl } from '../cluster'
23
import { parseMappingData, parseProductData, Magic, Version } from '../index'
34

45
test('Product', (done) => {
56
jest.setTimeout(60000)
6-
const url = clusterApiUrl('devnet')
7+
const url = getPythClusterApiUrl('pythtest-crosschain')
78
const oraclePublicKey = 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2'
89
const connection = new Connection(url)
910
const publicKey = new PublicKey(oraclePublicKey)

src/__tests__/PythNetworkRestClient.test.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { clusterApiUrl, Connection, PublicKey, SystemProgram } from '@solana/web3.js'
1+
import { Connection, PublicKey, SystemProgram } from '@solana/web3.js'
22
import { getPythProgramKeyForCluster, parseProductData, PythHttpClient } from '..'
3+
import { getPythClusterApiUrl } from '../cluster'
34

45
test('PythHttpClientCall: getData', (done) => {
5-
jest.setTimeout(20000)
6+
jest.setTimeout(60000)
67
try {
7-
const programKey = getPythProgramKeyForCluster('testnet')
8-
const currentConnection = new Connection(clusterApiUrl('testnet'))
8+
const programKey = getPythProgramKeyForCluster('pythtest-conformance')
9+
const currentConnection = new Connection(getPythClusterApiUrl('pythtest-conformance'))
910
const pyth_client = new PythHttpClient(currentConnection, programKey)
1011
pyth_client.getData().then(
1112
(result) => {
@@ -30,7 +31,7 @@ test('PythHttpClientCall: getAssetPricesFromAccounts for one account', (done) =>
3031
const solUSDKey = new PublicKey('7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K')
3132
try {
3233
const programKey = getPythProgramKeyForCluster('testnet')
33-
const currentConnection = new Connection(clusterApiUrl('testnet'))
34+
const currentConnection = new Connection(getPythClusterApiUrl('pythtest-conformance'))
3435
const pyth_client = new PythHttpClient(currentConnection, programKey)
3536
pyth_client
3637
.getAssetPricesFromAccounts([solUSDKey])
@@ -66,7 +67,7 @@ test('PythHttpClientCall: getAssetPricesFromAccounts for multiple accounts', (do
6667

6768
try {
6869
const programKey = getPythProgramKeyForCluster('testnet')
69-
const currentConnection = new Connection(clusterApiUrl('testnet'))
70+
const currentConnection = new Connection(getPythClusterApiUrl('pythtest-conformance'))
7071
const pyth_client = new PythHttpClient(currentConnection, programKey)
7172
pyth_client
7273
.getAssetPricesFromAccounts([solUSDKey, bonkUSDKey, usdcUSDKey])
@@ -106,7 +107,7 @@ test('PythHttpClientCall: getAssetPricesFromAccounts should throw for invalid ac
106107

107108
try {
108109
const programKey = getPythProgramKeyForCluster('testnet')
109-
const currentConnection = new Connection(clusterApiUrl('testnet'))
110+
const currentConnection = new Connection(getPythClusterApiUrl('pythtest-conformance'))
110111
const pyth_client = new PythHttpClient(currentConnection, programKey)
111112
pyth_client
112113
.getAssetPricesFromAccounts([solUSDKey, systemProgram, usdcUSDKey])

src/anchor/idl.json

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "2.20.0",
2+
"version": "2.33.0",
33
"name": "pyth_oracle",
44
"instructions": [
55
{
@@ -171,7 +171,7 @@
171171
},
172172
{
173173
"name": "permissionsAccount",
174-
"isMut": false,
174+
"isMut": true,
175175
"isSigner": false,
176176
"pda": {
177177
"seeds": [
@@ -640,6 +640,37 @@
640640
}
641641
}
642642
]
643+
},
644+
{
645+
"name": "initPriceFeedIndex",
646+
"discriminant": { "value": [2, 0, 0, 0, 19, 0, 0, 0] },
647+
"accounts": [
648+
{
649+
"name": "fundingAccount",
650+
"isMut": true,
651+
"isSigner": true
652+
},
653+
{
654+
"name": "priceAccount",
655+
"isMut": true,
656+
"isSigner": false
657+
},
658+
{
659+
"name": "permissionsAccount",
660+
"isMut": true,
661+
"isSigner": false,
662+
"pda": {
663+
"seeds": [
664+
{
665+
"kind": "const",
666+
"type": "string",
667+
"value": "permissions"
668+
}
669+
]
670+
}
671+
}
672+
],
673+
"args": []
643674
}
644675
],
645676
"types": [

src/anchor/program.ts

+33-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function pythOracleCoder(): PythOracleCoder {
1919
export { default as pythIdl } from './idl.json'
2020

2121
export type PythOracle = {
22-
version: '2.20.0'
22+
version: '2.33.0'
2323
name: 'pyth_oracle'
2424
instructions: [
2525
{
@@ -191,7 +191,7 @@ export type PythOracle = {
191191
},
192192
{
193193
name: 'permissionsAccount'
194-
isMut: false
194+
isMut: true
195195
isSigner: false
196196
pda: {
197197
seeds: [
@@ -661,6 +661,37 @@ export type PythOracle = {
661661
},
662662
]
663663
},
664+
{
665+
name: 'initPriceFeedIndex'
666+
discriminant: { value: [2, 0, 0, 0, 19, 0, 0, 0] }
667+
accounts: [
668+
{
669+
name: 'fundingAccount'
670+
isMut: true
671+
isSigner: true
672+
},
673+
{
674+
name: 'priceAccount'
675+
isMut: true
676+
isSigner: false
677+
},
678+
{
679+
name: 'permissionsAccount'
680+
isMut: true
681+
isSigner: false
682+
pda: {
683+
seeds: [
684+
{
685+
kind: 'const'
686+
type: 'string'
687+
value: 'permissions'
688+
},
689+
]
690+
}
691+
},
692+
]
693+
args: []
694+
},
664695
]
665696
types: [
666697
{

src/cluster.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function getPythClusterApiUrl(cluster: PythCluster): string {
3232
if (cluster === 'pythtest-conformance' || cluster === 'pythtest-crosschain') {
3333
return 'https://api.pythtest.pyth.network'
3434
} else if (cluster === 'pythnet') {
35-
return 'https://pythnet.rpcpool.com'
35+
return 'https://api2.pythnet.pyth.network'
3636
} else if (cluster === 'localnet') {
3737
return 'http://localhost:8899'
3838
} else {

src/index.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export enum AccountType {
4141
Permission,
4242
}
4343

44+
export type Flags = {
45+
accumulatorV2: boolean,
46+
messageBufferCleared: boolean,
47+
}
48+
4449
const empty32Buffer = Buffer.alloc(32)
4550
const PKorNull = (data: Buffer) => (data.equals(empty32Buffer) ? null : new PublicKey(data))
4651

@@ -105,8 +110,8 @@ export interface PriceData extends Base {
105110
minPublishers: number
106111
messageSent: number
107112
maxLatency: number
108-
drv3: number
109-
drv4: number
113+
flags: Flags,
114+
feedIndex: number
110115
productAccountKey: PublicKey
111116
nextPriceAccountKey: PublicKey | null
112117
previousSlot: bigint
@@ -290,10 +295,18 @@ export const parsePriceData = (data: Buffer, currentSlot?: number): PriceData =>
290295
const messageSent = data.readUInt8(105)
291296
// configurable max latency in slots between send and receive
292297
const maxLatency = data.readUInt8(106)
293-
// space for future derived values
294-
const drv3 = data.readInt8(107)
295-
// space for future derived values
296-
const drv4 = data.readInt32LE(108)
298+
// Various flags (used for operations)
299+
const flagBits = data.readInt8(107)
300+
301+
/* tslint:disable:no-bitwise */
302+
const flags = {
303+
accumulatorV2: (flagBits & (1<<0)) !== 0,
304+
messageBufferCleared: (flagBits & (1<<1)) !== 0,
305+
}
306+
/* tslint:enable:no-bitwise */
307+
308+
// Globally immutable unique price feed index used for publishing.
309+
const feedIndex = data.readInt32LE(108)
297310
// product id / reference account
298311
const productAccountKey = new PublicKey(data.slice(112, 144))
299312
// next price account in list
@@ -355,8 +368,8 @@ export const parsePriceData = (data: Buffer, currentSlot?: number): PriceData =>
355368
minPublishers,
356369
messageSent,
357370
maxLatency,
358-
drv3,
359-
drv4,
371+
flags,
372+
feedIndex,
360373
productAccountKey,
361374
nextPriceAccountKey,
362375
previousSlot,

0 commit comments

Comments
 (0)