Skip to content

Commit 6821430

Browse files
jayantkJayant Krishnamurthycctdaniel
authored
Add a verbose callback with access to solana account data (#38)
* Add verbose callbacks * update example * updates * remove unused imports * bump version Co-authored-by: Jayant Krishnamurthy <[email protected]> Co-authored-by: Daniel Chew <[email protected]>
1 parent 5a9377d commit 6821430

File tree

4 files changed

+55
-23
lines changed

4 files changed

+55
-23
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 2.8.0
4+
- Added `onPriceChangeVerbose` callback to `PythConnection` to support getting account keys and slots on each price update.
5+
36
## 2.7.2
47
### Changed
58
- Added pythtest program key and cluster url

package.json

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

src/PythConnection.ts

+46-20
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
1-
import { Connection, PublicKey, clusterApiUrl, Cluster, Commitment, AccountInfo, Account } from '@solana/web3.js'
1+
import { Connection, PublicKey, Commitment, AccountInfo } from '@solana/web3.js'
22
import {
3-
Base,
4-
Magic,
5-
parseMappingData,
63
parseBaseData,
74
parsePriceData,
85
parseProductData,
9-
Price,
106
PriceData,
117
Product,
128
ProductData,
13-
Version,
149
AccountType,
15-
MAX_SLOT_DIFFERENCE,
16-
PriceStatus,
1710
} from './index'
1811

1912
const ONES = '11111111111111111111111111111111'
2013

14+
/** An update to the content of the solana account at `key` that occurred at `slot`. */
15+
export type AccountUpdate<T> = {
16+
key: PublicKey
17+
accountInfo: AccountInfo<T>
18+
slot: number
19+
}
20+
2121
/**
2222
* Type of callback invoked whenever a pyth price account changes. The callback additionally
23-
* gets access product, which contains the metadata for this price account (e.g., that the symbol is "BTC/USD")
23+
* gets `product`, which contains the metadata for this price account (e.g., that the symbol is "BTC/USD")
2424
*/
2525
export type PythPriceCallback = (product: Product, price: PriceData) => void
2626

27+
/**
28+
* A price callback that additionally includes the raw solana account information. Use this if you need
29+
* access to account keys and such.
30+
*/
31+
export type PythVerbosePriceCallback = (product: AccountUpdate<ProductData>, price: AccountUpdate<PriceData>) => void
32+
2733
/**
2834
* Reads Pyth price data from a solana web3 connection. This class uses a callback-driven model,
2935
* similar to the solana web3 methods for tracking updates to accounts.
@@ -33,22 +39,29 @@ export class PythConnection {
3339
pythProgramKey: PublicKey
3440
commitment: Commitment
3541

36-
productAccountKeyToProduct: Record<string, Product> = {}
42+
productAccountKeyToProduct: Record<string, AccountUpdate<ProductData>> = {}
3743
priceAccountKeyToProductAccountKey: Record<string, string> = {}
3844

39-
callbacks: PythPriceCallback[] = []
45+
callbacks: PythVerbosePriceCallback[] = []
4046

41-
private handleProductAccount(key: PublicKey, account: AccountInfo<Buffer>) {
42-
const { priceAccountKey, type, product } = parseProductData(account.data)
43-
this.productAccountKeyToProduct[key.toString()] = product
44-
if (priceAccountKey.toString() !== ONES) {
45-
this.priceAccountKeyToProductAccountKey[priceAccountKey.toString()] = key.toString()
47+
private handleProductAccount(key: PublicKey, account: AccountInfo<Buffer>, slot: number) {
48+
const productData = parseProductData(account.data)
49+
this.productAccountKeyToProduct[key.toString()] = {
50+
key,
51+
slot,
52+
accountInfo: {
53+
...account,
54+
data: productData,
55+
},
56+
}
57+
if (productData.priceAccountKey.toString() !== ONES) {
58+
this.priceAccountKeyToProductAccountKey[productData.priceAccountKey.toString()] = key.toString()
4659
}
4760
}
4861

4962
private handlePriceAccount(key: PublicKey, account: AccountInfo<Buffer>, slot: number) {
50-
const product = this.productAccountKeyToProduct[this.priceAccountKeyToProductAccountKey[key.toString()]]
51-
if (product === undefined) {
63+
const productUpdate = this.productAccountKeyToProduct[this.priceAccountKeyToProductAccountKey[key.toString()]]
64+
if (productUpdate === undefined) {
5265
// This shouldn't happen since we're subscribed to all of the program's accounts,
5366
// but let's be good defensive programmers.
5467
throw new Error(
@@ -57,9 +70,17 @@ export class PythConnection {
5770
}
5871

5972
const priceData = parsePriceData(account.data, slot)
73+
const priceUpdate = {
74+
key,
75+
slot,
76+
accountInfo: {
77+
...account,
78+
data: priceData,
79+
},
80+
}
6081

6182
for (const callback of this.callbacks) {
62-
callback(product, priceData)
83+
callback(productUpdate, priceUpdate)
6384
}
6485
}
6586

@@ -72,7 +93,7 @@ export class PythConnection {
7293
// We can skip these because we're going to get every account owned by this program anyway.
7394
break
7495
case AccountType.Product:
75-
this.handleProductAccount(key, account)
96+
this.handleProductAccount(key, account, slot)
7697
break
7798
case AccountType.Price:
7899
if (!productOnly) {
@@ -117,6 +138,11 @@ export class PythConnection {
117138

118139
/** Register callback to receive price updates. */
119140
public onPriceChange(callback: PythPriceCallback) {
141+
this.callbacks.push((product, price) => callback(product.accountInfo.data.product, price.accountInfo.data))
142+
}
143+
144+
/** Register a verbose callback to receive price updates. */
145+
public onPriceChangeVerbose(callback: PythVerbosePriceCallback) {
120146
this.callbacks.push(callback)
121147
}
122148

src/example_ws_usage.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ import { PythConnection } from './PythConnection'
33
import { getPythClusterApiUrl, getPythProgramKeyForCluster, PythCluster } from './cluster'
44
import { PriceStatus } from '.'
55

6-
const SOLANA_CLUSTER_NAME: PythCluster = 'mainnet-beta'
6+
const SOLANA_CLUSTER_NAME: PythCluster = 'devnet'
77
const connection = new Connection(getPythClusterApiUrl(SOLANA_CLUSTER_NAME))
88
const pythPublicKey = getPythProgramKeyForCluster(SOLANA_CLUSTER_NAME)
99

1010
const pythConnection = new PythConnection(connection, pythPublicKey)
11-
pythConnection.onPriceChange((product, price) => {
11+
pythConnection.onPriceChangeVerbose((productAccount, priceAccount) => {
12+
// The arguments to the callback include solana account information / the update slot if you need it.
13+
const product = productAccount.accountInfo.data.product;
14+
const price = priceAccount.accountInfo.data;
1215
// sample output:
1316
// SRM/USD: $8.68725 ±$0.0131
1417
if (price.price && price.confidence) {

0 commit comments

Comments
 (0)