Skip to content

Commit 3de7232

Browse files
authored
Feature/no websocket (#15)
* PythNetworkHTTPClient can use Pyth Network data without a websocket * export PythNetworkHTTPClient * new test for PythNetworkHTTPClient * PythHttpClient refactoring * New PythHttpClientResult to getData * productPriceArray renamed to prices * Removed not used imports * Direct call to handlePriceAccount * PythHttpClient refactoring for thread safe procedure * Property shorthand suggested by lint * Indentation from 4 to 2 spaces
1 parent 417672b commit 3de7232

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

src/PythHttpClient.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { Commitment, Connection, PublicKey } from "@solana/web3.js";
2+
import { Product, PriceData, parseProductData, parsePriceData, parseBaseData, AccountType } from ".";
3+
4+
export interface PythHttpClientResult {
5+
assetTypes: string[];
6+
symbols: string[];
7+
products: Product[];
8+
productFromSymbol: Map<string, Product>;
9+
productPrice: Map<string, PriceData>;
10+
prices: PriceData[];
11+
}
12+
13+
/**
14+
* Reads Pyth price data from a solana web3 connection. This class uses a single HTTP call.
15+
* Use the method getData() to get updated prices values.
16+
*/
17+
export class PythHttpClient {
18+
connection: Connection;
19+
pythProgramKey: PublicKey;
20+
commitment: Commitment;
21+
22+
constructor(connection: Connection, pythProgramKey: PublicKey, commitment: Commitment = 'finalized') {
23+
this.connection = connection;
24+
this.pythProgramKey = pythProgramKey;
25+
this.commitment = commitment;
26+
}
27+
28+
/*
29+
* Get Pyth Network account information and return actual price state.
30+
* The result contains lists of asset types, product symbols and their prices.
31+
*/
32+
public async getData(): Promise<PythHttpClientResult> {
33+
const assetTypes = new Set<string>();
34+
const productSymbols = new Set<string>();
35+
const products = new Set<Product>()
36+
const productFromSymbol = new Map<string, Product>()
37+
const productPrice = new Map<string, PriceData>()
38+
const prices = new Array<PriceData>();
39+
40+
// Retrieve data from blockchain
41+
const accountList = await this.connection.getProgramAccounts(this.pythProgramKey, this.commitment);
42+
43+
// Popolate producs and prices
44+
const priceDataQueue = new Array<PriceData>();
45+
const productAccountKeyToProduct = new Map<string, Product>();
46+
47+
accountList.forEach(singleAccount => {
48+
const base = parseBaseData(singleAccount.account.data);
49+
if (base) {
50+
switch (AccountType[base.type]) {
51+
case 'Mapping':
52+
// We can skip these because we're going to get every account owned by this program anyway.
53+
break;
54+
case 'Product':
55+
const productData = parseProductData(singleAccount.account.data)
56+
57+
productAccountKeyToProduct.set(singleAccount.pubkey.toBase58(), productData.product)
58+
assetTypes.add(productData.product.asset_type);
59+
productSymbols.add(productData.product.symbol);
60+
products.add(productData.product);
61+
productFromSymbol.set(productData.product.symbol, productData.product);
62+
break;
63+
case 'Price':
64+
const priceData = parsePriceData(singleAccount.account.data)
65+
priceDataQueue.push(priceData)
66+
break;
67+
case 'Test':
68+
break;
69+
default:
70+
throw new Error(`Unknown account type: ${base.type}. Try upgrading pyth-client.`)
71+
}
72+
}
73+
});
74+
75+
priceDataQueue.forEach(priceData => {
76+
const product = productAccountKeyToProduct.get(priceData.productAccountKey.toBase58())
77+
78+
if (product) {
79+
productPrice.set(product.symbol, priceData);
80+
prices.push(priceData);
81+
}
82+
});
83+
84+
const result: PythHttpClientResult = {
85+
assetTypes: Array.from(assetTypes),
86+
symbols: Array.from(productSymbols),
87+
products: Array.from(products),
88+
productFromSymbol,
89+
productPrice,
90+
prices
91+
};
92+
93+
return result;
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { clusterApiUrl, Connection } from "@solana/web3.js";
2+
import { getPythProgramKeyForCluster, PythHttpClient } from "..";
3+
4+
test('PythHttpClientCall', done => {
5+
jest.setTimeout(20000)
6+
try {
7+
const programKey = getPythProgramKeyForCluster('testnet');
8+
const currentConnection = new Connection(clusterApiUrl('testnet'));
9+
const pyth_client = new PythHttpClient(currentConnection, programKey);
10+
pyth_client.getData().then(
11+
result => {
12+
try {
13+
console.log("products number: ", result.products.length)
14+
console.log("asset types: ", result.assetTypes);
15+
console.log("product symbols: ", result.symbols);
16+
17+
// Find a product with symbol "SOL/USD"
18+
const products = result.products.filter(p => p.symbol === "SOL/USD");
19+
expect(products.length).toBeGreaterThan(0);
20+
21+
// Find product prices
22+
const price = result.productPrice.get(products[0].symbol);
23+
expect(price).toBeDefined();
24+
25+
console.log("products", products)
26+
console.log("price", price)
27+
28+
done()
29+
} catch (cerr) {
30+
done(cerr)
31+
}
32+
},
33+
err => done(err)
34+
)
35+
} catch (err_catch) {
36+
done(err_catch);
37+
}
38+
});

src/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -336,5 +336,6 @@ export const parsePriceData = (data: Buffer): PriceData => {
336336
}
337337
}
338338

339-
export { PythConnection } from './PythConnection'
340-
export { getPythProgramKeyForCluster } from './cluster'
339+
export { PythConnection } from './PythConnection';
340+
export { PythHttpClient } from './PythHttpClient';
341+
export { getPythProgramKeyForCluster } from './cluster';

0 commit comments

Comments
 (0)