Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/PythHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,33 @@ export class PythHttpClient {

return result
}

/**
* Get the price state for an array of specified price accounts.
* The result is the price state for the given assets if they exist, throws if at least one account does not exist.
*/
public async getAssetPricesFromAccounts(priceAccounts: PublicKey[]): Promise<PriceData[]> {
const priceDatas: PriceData[] = []
const currentSlotPromise = this.connection.getSlot(this.commitment)
const accountInfos = await this.connection.getMultipleAccountsInfo(priceAccounts, this.commitment)

const currentSlot = await currentSlotPromise
for (let i = 0; i < priceAccounts.length; i++) {
// Declare local variable to silence typescript warning; otherwise it thinks accountInfos[i] can be undefined
const accInfo = accountInfos[i]
if (!accInfo) {
throw new Error('Could not get account info for account ' + priceAccounts[i].toBase58())
}

const baseData = parseBaseData(accInfo.data)
if (baseData === undefined || baseData.type !== AccountType.Price) {
throw new Error('Account ' + priceAccounts[i].toBase58() + ' is not a price account')
}

const priceData = parsePriceData(accInfo.data, currentSlot)
priceDatas.push(priceData)
}

return priceDatas
}
}
27 changes: 0 additions & 27 deletions src/__tests__/PythNetworkRestClient-test.ts

This file was deleted.

102 changes: 99 additions & 3 deletions src/__tests__/PythNetworkRestClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { clusterApiUrl, Connection } from '@solana/web3.js'
import { getPythProgramKeyForCluster, PythHttpClient } from '..'
import { clusterApiUrl, Connection, PublicKey, SystemProgram } from '@solana/web3.js'
import { getPythProgramKeyForCluster, parseProductData, PythHttpClient } from '..'

test('PythHttpClientCall', (done) => {
test('PythHttpClientCall: getData', (done) => {
jest.setTimeout(20000)
try {
const programKey = getPythProgramKeyForCluster('testnet')
Expand All @@ -25,3 +25,99 @@ test('PythHttpClientCall', (done) => {
done(err_catch)
}
})

test('PythHttpClientCall: getAssetPricesFromAccounts for one account', (done) => {
const solUSDKey = new PublicKey('7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K')
try {
const programKey = getPythProgramKeyForCluster('testnet')
const currentConnection = new Connection(clusterApiUrl('testnet'))
const pyth_client = new PythHttpClient(currentConnection, programKey)
pyth_client
.getAssetPricesFromAccounts([solUSDKey])
.then((result) => {
try {
expect(result.length).toBe(1)
// Check the symbol through the product account
const productAcc = result[0].productAccountKey

return currentConnection.getAccountInfo(productAcc)
} catch (cerr) {
done(cerr)
}
})
.then((productAcc) => {
if (!productAcc) {
done(new Error('Product account not found'))
}
// We can assert it's defined here because we call done otherwise above
const productData = parseProductData(productAcc!.data)
expect(productData.product.symbol).toBe('Crypto.SOL/USD')
done()
})
} catch (err_catch) {
done(err_catch)
}
}, 20000)

test('PythHttpClientCall: getAssetPricesFromAccounts for multiple accounts', (done) => {
const solUSDKey = new PublicKey('7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K')
const bonkUSDKey = new PublicKey('FPPnzp74SGt72T463B62fQh3Di9fXrBe82YnQh8ycQp9')
const usdcUSDKey = new PublicKey('GBvYgUMCt4nvycUZMEBpHyLEXGbKjr6G9HjMjmLyf6mA')

try {
const programKey = getPythProgramKeyForCluster('testnet')
const currentConnection = new Connection(clusterApiUrl('testnet'))
const pyth_client = new PythHttpClient(currentConnection, programKey)
pyth_client
.getAssetPricesFromAccounts([solUSDKey, bonkUSDKey, usdcUSDKey])
.then((result) => {
try {
expect(result.length).toBe(3)
// Check the symbol through the product account
const productAccs = result.map((r) => r.productAccountKey)

return currentConnection.getMultipleAccountsInfo(productAccs)
} catch (cerr) {
done(cerr)
}
})
.then((productAccs) => {
// We can assert it's defined here because we call done otherwise above
const expectedSymbols = ['Crypto.SOL/USD', 'Crypto.BONK/USD', 'Crypto.USDC/USD']
productAccs!.forEach((acc, i) => {
if (!acc) {
done(new Error('Product account not found'))
}
const productData = parseProductData(acc!.data)
expect(productData.product.symbol).toBe(expectedSymbols[i])
})
done()
})
} catch (err_catch) {
done(err_catch)
}
}, 20000)

test('PythHttpClientCall: getAssetPricesFromAccounts should throw for invalid account inclusion', (done) => {
const solUSDKey = new PublicKey('7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K')
// Should never be a pricefeed
const systemProgram = SystemProgram.programId
const usdcUSDKey = new PublicKey('GBvYgUMCt4nvycUZMEBpHyLEXGbKjr6G9HjMjmLyf6mA')

try {
const programKey = getPythProgramKeyForCluster('testnet')
const currentConnection = new Connection(clusterApiUrl('testnet'))
const pyth_client = new PythHttpClient(currentConnection, programKey)
pyth_client
.getAssetPricesFromAccounts([solUSDKey, systemProgram, usdcUSDKey])
.then((result) => {
done(new Error('Should not have gotten here'))
})
.catch((err) => {
expect(err.message).toBe('Account ' + systemProgram.toBase58() + ' is not a price account')
done()
})
} catch (err_catch) {
done(err_catch)
}
}, 20000)