Skip to content

Commit e724b2f

Browse files
Add method to fetch individual prices specified by user (#61)
* add getAssetPricesFromAccounts method * add check for price data format * add getAssetPricesFromAccounts tests * remove duplicate test
1 parent b61b454 commit e724b2f

File tree

3 files changed

+128
-30
lines changed

3 files changed

+128
-30
lines changed

src/PythHttpClient.ts

+29
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,33 @@ export class PythHttpClient {
114114

115115
return result
116116
}
117+
118+
/**
119+
* Get the price state for an array of specified price accounts.
120+
* The result is the price state for the given assets if they exist, throws if at least one account does not exist.
121+
*/
122+
public async getAssetPricesFromAccounts(priceAccounts: PublicKey[]): Promise<PriceData[]> {
123+
const priceDatas: PriceData[] = []
124+
const currentSlotPromise = this.connection.getSlot(this.commitment)
125+
const accountInfos = await this.connection.getMultipleAccountsInfo(priceAccounts, this.commitment)
126+
127+
const currentSlot = await currentSlotPromise
128+
for (let i = 0; i < priceAccounts.length; i++) {
129+
// Declare local variable to silence typescript warning; otherwise it thinks accountInfos[i] can be undefined
130+
const accInfo = accountInfos[i]
131+
if (!accInfo) {
132+
throw new Error('Could not get account info for account ' + priceAccounts[i].toBase58())
133+
}
134+
135+
const baseData = parseBaseData(accInfo.data)
136+
if (baseData === undefined || baseData.type !== AccountType.Price) {
137+
throw new Error('Account ' + priceAccounts[i].toBase58() + ' is not a price account')
138+
}
139+
140+
const priceData = parsePriceData(accInfo.data, currentSlot)
141+
priceDatas.push(priceData)
142+
}
143+
144+
return priceDatas
145+
}
117146
}

src/__tests__/PythNetworkRestClient-test.ts

-27
This file was deleted.

src/__tests__/PythNetworkRestClient.test.ts

+99-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { clusterApiUrl, Connection } from '@solana/web3.js'
2-
import { getPythProgramKeyForCluster, PythHttpClient } from '..'
1+
import { clusterApiUrl, Connection, PublicKey, SystemProgram } from '@solana/web3.js'
2+
import { getPythProgramKeyForCluster, parseProductData, PythHttpClient } from '..'
33

4-
test('PythHttpClientCall', (done) => {
4+
test('PythHttpClientCall: getData', (done) => {
55
jest.setTimeout(20000)
66
try {
77
const programKey = getPythProgramKeyForCluster('testnet')
@@ -25,3 +25,99 @@ test('PythHttpClientCall', (done) => {
2525
done(err_catch)
2626
}
2727
})
28+
29+
test('PythHttpClientCall: getAssetPricesFromAccounts for one account', (done) => {
30+
const solUSDKey = new PublicKey('7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K')
31+
try {
32+
const programKey = getPythProgramKeyForCluster('testnet')
33+
const currentConnection = new Connection(clusterApiUrl('testnet'))
34+
const pyth_client = new PythHttpClient(currentConnection, programKey)
35+
pyth_client
36+
.getAssetPricesFromAccounts([solUSDKey])
37+
.then((result) => {
38+
try {
39+
expect(result.length).toBe(1)
40+
// Check the symbol through the product account
41+
const productAcc = result[0].productAccountKey
42+
43+
return currentConnection.getAccountInfo(productAcc)
44+
} catch (cerr) {
45+
done(cerr)
46+
}
47+
})
48+
.then((productAcc) => {
49+
if (!productAcc) {
50+
done(new Error('Product account not found'))
51+
}
52+
// We can assert it's defined here because we call done otherwise above
53+
const productData = parseProductData(productAcc!.data)
54+
expect(productData.product.symbol).toBe('Crypto.SOL/USD')
55+
done()
56+
})
57+
} catch (err_catch) {
58+
done(err_catch)
59+
}
60+
}, 20000)
61+
62+
test('PythHttpClientCall: getAssetPricesFromAccounts for multiple accounts', (done) => {
63+
const solUSDKey = new PublicKey('7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K')
64+
const bonkUSDKey = new PublicKey('FPPnzp74SGt72T463B62fQh3Di9fXrBe82YnQh8ycQp9')
65+
const usdcUSDKey = new PublicKey('GBvYgUMCt4nvycUZMEBpHyLEXGbKjr6G9HjMjmLyf6mA')
66+
67+
try {
68+
const programKey = getPythProgramKeyForCluster('testnet')
69+
const currentConnection = new Connection(clusterApiUrl('testnet'))
70+
const pyth_client = new PythHttpClient(currentConnection, programKey)
71+
pyth_client
72+
.getAssetPricesFromAccounts([solUSDKey, bonkUSDKey, usdcUSDKey])
73+
.then((result) => {
74+
try {
75+
expect(result.length).toBe(3)
76+
// Check the symbol through the product account
77+
const productAccs = result.map((r) => r.productAccountKey)
78+
79+
return currentConnection.getMultipleAccountsInfo(productAccs)
80+
} catch (cerr) {
81+
done(cerr)
82+
}
83+
})
84+
.then((productAccs) => {
85+
// We can assert it's defined here because we call done otherwise above
86+
const expectedSymbols = ['Crypto.SOL/USD', 'Crypto.BONK/USD', 'Crypto.USDC/USD']
87+
productAccs!.forEach((acc, i) => {
88+
if (!acc) {
89+
done(new Error('Product account not found'))
90+
}
91+
const productData = parseProductData(acc!.data)
92+
expect(productData.product.symbol).toBe(expectedSymbols[i])
93+
})
94+
done()
95+
})
96+
} catch (err_catch) {
97+
done(err_catch)
98+
}
99+
}, 20000)
100+
101+
test('PythHttpClientCall: getAssetPricesFromAccounts should throw for invalid account inclusion', (done) => {
102+
const solUSDKey = new PublicKey('7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K')
103+
// Should never be a pricefeed
104+
const systemProgram = SystemProgram.programId
105+
const usdcUSDKey = new PublicKey('GBvYgUMCt4nvycUZMEBpHyLEXGbKjr6G9HjMjmLyf6mA')
106+
107+
try {
108+
const programKey = getPythProgramKeyForCluster('testnet')
109+
const currentConnection = new Connection(clusterApiUrl('testnet'))
110+
const pyth_client = new PythHttpClient(currentConnection, programKey)
111+
pyth_client
112+
.getAssetPricesFromAccounts([solUSDKey, systemProgram, usdcUSDKey])
113+
.then((result) => {
114+
done(new Error('Should not have gotten here'))
115+
})
116+
.catch((err) => {
117+
expect(err.message).toBe('Account ' + systemProgram.toBase58() + ' is not a price account')
118+
done()
119+
})
120+
} catch (err_catch) {
121+
done(err_catch)
122+
}
123+
}, 20000)

0 commit comments

Comments
 (0)