From 6f64b843bf1a4476dc80c351efa10790bee896a7 Mon Sep 17 00:00:00 2001 From: jacksturt <117492794+jacksturt@users.noreply.github.com> Date: Mon, 6 Feb 2023 10:37:34 -0700 Subject: [PATCH] update PythConnection to subscribe to single accounts and add new ws example with SOL/USDC feed (#64) --- package-lock.json | 4 ++-- package.json | 3 ++- src/PythConnection.ts | 39 +++++++++++++++++++++++++++-------- src/example_ws_single_feed.ts | 30 +++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 src/example_ws_single_feed.ts diff --git a/package-lock.json b/package-lock.json index 310813a..07596c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pythnetwork/client", - "version": "2.13.1", + "version": "2.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@pythnetwork/client", - "version": "2.13.1", + "version": "2.14.0", "license": "Apache-2.0", "dependencies": { "@coral-xyz/anchor": "^0.26.0", diff --git a/package.json b/package.json index a90d624..875660b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/client", - "version": "2.13.1", + "version": "2.14.0", "description": "Client for consuming Pyth price data", "homepage": "https://pyth.network", "main": "lib/index.js", @@ -20,6 +20,7 @@ "version": "npm run format && git add -A src", "postversion": "git push && git push --tags", "ws_example": "npm run build && node lib/example_ws_usage.js", + "ws_single_example": "npm run build && node lib/example_ws_usage.js", "http_example": "npm run build && node lib/example_http_usage.js" }, "keywords": [ diff --git a/src/PythConnection.ts b/src/PythConnection.ts index 4812dfd..ac60318 100644 --- a/src/PythConnection.ts +++ b/src/PythConnection.ts @@ -30,6 +30,7 @@ export class PythConnection { connection: Connection pythProgramKey: PublicKey commitment: Commitment + feedIds?: PublicKey[] productAccountKeyToProduct: Record> = {} priceAccountKeyToProductAccountKey: Record = {} @@ -104,29 +105,49 @@ export class PythConnection { /** Create a PythConnection that reads its data from an underlying solana web3 connection. * pythProgramKey is the public key of the Pyth program running on the chosen solana cluster. */ - constructor(connection: Connection, pythProgramKey: PublicKey, commitment: Commitment = 'finalized') { + constructor(connection: Connection, pythProgramKey: PublicKey, commitment: Commitment = 'finalized', feedIds?: PublicKey[]) { this.connection = connection this.pythProgramKey = pythProgramKey this.commitment = commitment + this.feedIds = feedIds } /** Start receiving price updates. Once this method is called, any registered callbacks will be invoked * each time a Pyth price account is updated. */ public async start() { - const accounts = await this.connection.getProgramAccounts(this.pythProgramKey, this.commitment) + let accounts = await this.connection.getProgramAccounts(this.pythProgramKey, this.commitment) const currentSlot = await this.connection.getSlot(this.commitment) + // Handle all accounts once since we need to handle product accounts + // at least once for (const account of accounts) { this.handleAccount(account.pubkey, account.account, true, currentSlot) } - this.connection.onProgramAccountChange( - this.pythProgramKey, - (keyedAccountInfo, context) => { - this.handleAccount(keyedAccountInfo.accountId, keyedAccountInfo.accountInfo, false, context.slot) - }, - this.commitment, - ) + if(this.feedIds) { + // Filter down to only the feeds we want + const rawIDs = this.feedIds.map((feed) => feed.toString()) + accounts = accounts.filter((feed) => rawIDs.includes(feed.pubkey.toString())) + for (const account of accounts){ + this.connection.onAccountChange( + account.pubkey, + + (accountInfo, context) => { + this.handleAccount(account.pubkey, accountInfo, false, context.slot) + }, + this.commitment, + ) + } + + } else { + this.connection.onProgramAccountChange( + this.pythProgramKey, + (keyedAccountInfo, context) => { + this.handleAccount(keyedAccountInfo.accountId, keyedAccountInfo.accountInfo, false, context.slot) + }, + this.commitment, + ) + } } /** Register callback to receive price updates. */ diff --git a/src/example_ws_single_feed.ts b/src/example_ws_single_feed.ts new file mode 100644 index 0000000..be52f91 --- /dev/null +++ b/src/example_ws_single_feed.ts @@ -0,0 +1,30 @@ +import { Connection, PublicKey } from '@solana/web3.js' +import { PythConnection } from './PythConnection' +import { getPythClusterApiUrl, getPythProgramKeyForCluster, PythCluster, PriceStatus } from '.' + +const PYTHNET_CLUSTER_NAME: PythCluster = 'pythnet' +const connection = new Connection(getPythClusterApiUrl(PYTHNET_CLUSTER_NAME)) +const pythPublicKey = getPythProgramKeyForCluster(PYTHNET_CLUSTER_NAME) +// This feed ID comes from this list: https://pyth.network/developers/price-feed-ids#solana-mainnet-beta +// This example shows Crypto.SOL/USD +const feeds = [new PublicKey('H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG')] + +const pythConnection = new PythConnection(connection, pythPublicKey, "confirmed", feeds) +pythConnection.onPriceChangeVerbose((productAccount, priceAccount) => { + // The arguments to the callback include solana account information / the update slot if you need it. + const product = productAccount.accountInfo.data.product + const price = priceAccount.accountInfo.data + // sample output: + // SOL/USD: $14.627930000000001 ±$0.01551797 + if (price.price && price.confidence) { + // tslint:disable-next-line:no-console + console.log(`${product.symbol}: $${price.price} \xB1$${price.confidence}`) + } else { + // tslint:disable-next-line:no-console + console.log(`${product.symbol}: price currently unavailable. status is ${PriceStatus[price.status]}`) + } +}) + +// tslint:disable-next-line:no-console +console.log('Reading from Pyth price feed...') +pythConnection.start()