Skip to content

Commit bb830e1

Browse files
guibescosjayantk
andauthored
docs(solana_sdk): Improve docs (#1414)
* Checkpoint * Checkpoint * Checkpoint * Checkpoint * fix: pusher * Checkpoint * Works * fix: pass pusher program id * Add docs * 0.1.0 * Bump npm package * Go * Comment * Add customizable shard id * Allow configurable priority fees * Update readme * Update text * readme updates * Readme * More text * More text * Review * Text * readme * add processed commitment * fix comment * whoops * Set compute units to a more reasonable value --------- Co-authored-by: Jayant Krishnamurthy <jayantkrishnamurthy@gmail.com>
1 parent 972a9a1 commit bb830e1

File tree

6 files changed

+496
-110
lines changed

6 files changed

+496
-110
lines changed
Lines changed: 159 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,192 @@
11
# Pyth Solana Receiver JS SDK
22

3-
This is a Javascript SDK to interact with the Pyth Solana Receiver contract whose code lives [here](/target_chains/solana).
3+
[@pythnetwork/pyth-solana-receiver](https://www.npmjs.com/package/@pythnetwork/pyth-solana-receiver) is a Typescript SDK for interacting with the Pyth Solana Receiver contract.
4+
The SDK enables users to construct transactions that post Pyth price updates to the Solana blockchain and use them in downstream applications.
45

5-
It is available on [npm](https://www.npmjs.com/package/@pythnetwork/pyth-solana-receiver).
6+
The code for the underlying Pyth Solana Receiver program lives [here](/target_chains/solana).
67

7-
## Pull model
8+
## Installation
89

9-
The Pyth Solana Receiver allows users to consume Pyth price updates on a pull basis. This means that the user is responsible for submitting the price data on-chain whenever they want to interact with an app that requires a price update.
10+
You can install the package using your favorite typescript version manager
1011

11-
Price updates get posted into price update accounts, owned by the Receiver contract. Once an update has been posted to a price update account, it can be used by anyone by simply passing the price update account as one of the accounts in a Solana instruction.
12-
Price update accounts can be closed by whoever wrote them to recover the rent.
12+
**NPM:** `npm install @pythnetwork/pyth-solana-receiver`
1313

14-
## Example use
14+
**Yarn:** `yarn add @pythnetwork/pyth-solana-receiver`
1515

16-
```ts
17-
import { Connection, PublicKey } from "@solana/web3.js";
18-
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
16+
## Preliminaries
17+
18+
### Accessing Pyth Prices
19+
20+
This SDK is designed to be used in combination with a source of Pyth pricing data.
21+
There are two different sources of pricing data that users can choose from.
22+
23+
- [Hermes](https://docs.pyth.network/price-feeds/pythnet-price-feeds/hermes) is a webservice that provides HTTP and websocket endpoints for retrieving real-time Pyth prices.
24+
The example code below uses the public Hermes instance hosted by the Pyth Data Association at `https://hermes.pyth.network/`.
25+
Hermes is also available from several infrastructure providers [listed here](https://docs.pyth.network/price-feeds/api-instances-and-providers/hermes).
26+
The [Price Service Client](https://github.com/pyth-network/pyth-crosschain/tree/main/price_service/client/js) can be used to access Hermes prices in a convenient way.
27+
- [Benchmarks](https://docs.pyth.network/benchmarks) is a webservice that provides HTTP endpoints for accessing historical Pyth prices.
28+
This service can be used for applications that require prices from specific times in the past.
29+
30+
Both of these services return Pyth price updates, which are binary blobs of signed and timestamped prices.
31+
This SDK enables users to post price updates to the Solana blockchain, verify their validity, and consume them in downstream Solana applications.
32+
33+
### Price Feed IDs
34+
35+
Pyth identifies each pair of assets (e.g., BTC/USD) with a unique price feed id.
36+
The price feed id is a UUID written as a hexadecimal string.
37+
In order to get the price for a specific pair of assets, you will need its corresponding price feed id.
38+
You can look up all available price feed ids [here](https://pyth.network/developers/price-feed-ids).
39+
40+
### Pyth Solana Receiver
41+
42+
The Pyth Solana Receiver provides two different methods for posting and using price updates.
43+
44+
First, a price update can be written to a **_price update account_**.
45+
Once the account has been written, other programs can read the Pyth price from the account by simply including it in their instruction.
46+
Price update accounts are ephemeral: they have an owner who can overwrite their contents or close the account.
47+
This method for using Pyth prices is a good fit for applications that need to use prices at specific timestamps (e.g., to settle a trade at a time).
48+
49+
Second, a price update can be written to a **_price feed account_**.
50+
A price feed account is designed to work similarly to a Pyth price feed -- it holds a sequence of price updates that move forward in time.
51+
Applications can therefore store the address of a price feed account and read its contents whenever they need a recent price for the feed.
52+
Price feed accounts have a fixed address derived from the feed id and a shard id.
53+
The shard id allows different applications to use different accounts for the same feed, thereby reducing the impact of solana congestion.
54+
This method of using Pyth prices is a good fit for applications that always want to use the most recent price.
55+
Additionally, the [Price Scheduler](../../../../../../../price_pusher/) can be used to continuously write fresh updates to a price feed account, freeing applications from worrying about writing their own updates.
56+
57+
This SDK provides methods for working with both types of accounts.
58+
59+
## Usage
60+
61+
The `PythSolanaReceiver` class is the main entrypoint for the SDK.
62+
Instantiate it with a Solana web3 `Connection` and anchor `Wallet`:
63+
64+
```typescript
1965
import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
20-
import { MyFirstPythApp, IDL } from "./idl/my_first_pyth_app";
66+
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
67+
import { Wallet } from "@coral-xyz/anchor";
68+
69+
const connection = new Connection("https://api.mainnet-beta.solana.com");
70+
const wallet = new Wallet(
71+
Keypair.fromSecretKey(/* <insert private key here> */)
72+
);
73+
const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet });
74+
```
75+
76+
### Post a price update
77+
78+
Post an update to an ephemeral price update account:
2179

22-
const SOL_PRICE_FEED_ID =
23-
"0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d";
24-
const ETH_PRICE_FEED_ID =
25-
"0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace";
80+
```ts
81+
// Fetch this from hermes or benchmarks. See Preliminaries section above for more info.
82+
const priceUpdateData =
83+
"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";
84+
85+
// Pass `closeUpdateAccounts: true` to the transaction builder constructor to automatically close the
86+
// price update accounts at the end of the sequence of transactions.
87+
const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
88+
closeUpdateAccounts: false,
89+
});
90+
await transactionBuilder.addPostPriceUpdates(priceUpdateData);
2691

27-
const priceServiceConnection = new PriceServiceConnection(
28-
"https://hermes.pyth.network/",
29-
{ priceFeedRequestConfig: { binary: true } }
92+
await transactionBuilder.addPriceConsumerInstructions(
93+
async (
94+
getPriceUpdateAccount: (priceFeedId: string) => PublicKey
95+
): Promise<InstructionWithEphemeralSigners[]> => {
96+
// Generate instructions here that use the price updates posted above.
97+
// getPriceUpdateAccount(<price feed id>) will give you the account for each price update.
98+
return [];
99+
}
30100
);
31-
const priceUpdateData = await priceServiceConnection.getLatestVaas([
32-
SOL_PRICE_FEED_ID,
33-
ETH_PRICE_FEED_ID,
34-
]); // Fetch off-chain price update data
35-
36-
const myFirstPythApp = new Program<MyFirstPythApp>(
37-
IDL as MyFirstPythApp,
38-
MY_FIRST_PYTH_APP_PROGRAM_ID,
39-
{}
101+
102+
// Send the instructions in the builder in 1 or more transactions.
103+
// The builder will pack the instructions into transactions automatically.
104+
await pythSolanaReceiver.provider.sendAll(
105+
await transactionBuilder.buildVersionedTransactions({
106+
computeUnitPriceMicroLamports: 100000,
107+
})
40108
);
109+
```
110+
111+
The code snippet above will post every price update in `priceUpdateData` to a new ephemeral account.
112+
113+
See `examples/post_price_update.ts` for a runnable example of posting a price update.
114+
115+
### Update a price feed account
116+
117+
Update the price feed account for shard id 1:
118+
119+
```typescript
120+
// Fetch this from hermes or benchmarks. See Preliminaries section above for more info.
121+
const priceUpdateData =
122+
"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";
41123

42124
const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({});
43-
await transactionBuilder.addPostPriceUpdates(priceUpdateData);
125+
// Update the price feed accounts for the feed ids in priceUpdateData and shard id 1
126+
await transactionBuilder.addUpdatePriceFeed(priceUpdateData, 1);
127+
44128
await transactionBuilder.addPriceConsumerInstructions(
45129
async (
46130
getPriceUpdateAccount: (priceFeedId: string) => PublicKey
47131
): Promise<InstructionWithEphemeralSigners[]> => {
48-
return [
49-
{
50-
instruction: await myFirstPythApp.methods
51-
.consume()
52-
.accounts({
53-
solPriceUpdate: getPriceUpdateAccount(SOL_PRICE_FEED_ID),
54-
ethPriceUpdate: getPriceUpdateAccount(ETH_PRICE_FEED_ID),
55-
})
56-
.instruction(),
57-
signers: [],
58-
},
59-
];
132+
// Generate instructions here that use the price updates posted above.
133+
// getPriceUpdateAccount(<price feed id>) will give you the account for each price update.
134+
return [];
60135
}
61136
);
137+
138+
// Send the instructions in the builder in 1 or more transactions.
139+
// The builder will pack the instructions into transactions automatically.
62140
await pythSolanaReceiver.provider.sendAll(
63141
await transactionBuilder.buildVersionedTransactions({
64-
computeUnitPriceMicroLamports: 1000000,
142+
computeUnitPriceMicroLamports: 100000,
65143
})
66144
);
67145
```
68146

69-
Alternatively you can use the instruction builder methods from `PythSolanaReceiver` :
147+
The code above will update the price feed accounts for the feeds in `priceUpdateData` (in this example, SOL/USD and ETH/USD).
148+
The address of the price feed accounts can be derived automatically from the feed id and the shard id:
70149

71-
```ts
72-
import { PublicKey } from "@solana/web3.js";
73-
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
74-
import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
75-
import { MyFirstPythApp, IDL } from "./idl/my_first_pyth_app";
150+
```typescript
151+
const solUsdPriceFeedAccount = pythSolanaReceiver
152+
.getPriceFeedAccountAddress(1, SOL_PRICE_FEED_ID)
153+
.toBase58();
154+
```
76155

77-
const SOL_PRICE_FEED_ID =
78-
"0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d";
79-
const ETH_PRICE_FEED_ID =
80-
"0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace";
156+
Note that the example above uses a shard id of 1.
157+
Changing the shard id to a different value will give you a different account address.
81158

82-
const priceServiceConnection = new PriceServiceConnection(
83-
"https://hermes.pyth.network/",
84-
{ priceFeedRequestConfig: { binary: true } }
85-
);
86-
const priceUpdateData = await priceServiceConnection.getLatestVaas([
87-
SOL_PRICE_FEED_ID,
88-
ETH_PRICE_FEED_ID,
89-
]); // Fetch off-chain price update data
159+
See `examples/update_price_feed.ts` for a runnable example of updating a price feed.
90160

91-
const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet });
92-
const { postInstructions, closeInstructions, priceFeedIdToPriceUpdateAccount } =
93-
await pythSolanaReceiver.buildPostPriceUpdateInstructions(priceUpdateData); // Get instructions to post the price update data and to close the accounts later
161+
### Partially verified price updates
94162

95-
const myFirstPythApp = new Program<MyFirstPythApp>(
96-
IDL as MyFirstPythApp,
97-
MY_FIRST_PYTH_APP_PROGRAM_ID,
98-
{}
99-
);
100-
const consumerInstruction: InstructionWithEphemeralSigners = {
101-
instruction: await myFirstPythApp.methods
102-
.consume()
103-
.accounts({
104-
solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID],
105-
ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID],
106-
})
107-
.instruction(),
108-
signers: [],
109-
};
110-
111-
const transactions = pythSolanaReceiver.batchIntoVersionedTransactions(
112-
[...postInstructions, consumerInstruction, ...closeInstructions],
113-
{ computeUnitPriceMicroLamports: 1000000 }
114-
); // Put all the instructions together
115-
await pythSolanaReceiver.provider.sendAll(transactions);
163+
Price updates are relatively large and can take multiple transactions to post on the blockchain.
164+
You can reduce the size of the transaction payload by using `addPostPartiallyVerifiedPriceUpdates` instead of `addPostPriceUpdates`.
165+
This method does sacrifice some security however -- please see the method documentation for more details.
166+
167+
### Get Instructions
168+
169+
The `PythTransactionBuilder` class used in the examples above helps craft transactions that update prices and then use them in successive instructions.
170+
However, if you would like to craft your own transactions, `PythSolanaReceiver` exposes several methods for constructing the instructions for working with both price update accounts and price feed accounts.
171+
See `examples/post_price_update_instructions.ts` for an example of how to work with instructions.
172+
173+
## Examples
174+
175+
This SDK includes several runnable examples in the `examples/` directory.
176+
You can run these examples by performing the following steps.
177+
First, install and build any necessary typescript dependencies:
178+
179+
1. Clone the `pyth-crosschain` git repo
180+
2. Run `npm install` in the root of the repo
181+
3. Run `npx lerna run build` anywhere in the repo
182+
4. From the `pyth_solana_receiver` directory, run `npx ts-node examples/<example filename>.ts`
183+
184+
The examples require a Solana keypair with SOL to send Solana transactions.
185+
By default, the examples will use the same Solana keypair used by the Solana CLI (at `~/.config/solana/id.json`).
186+
You can override this default by setting the `SOLANA_KEYPAIR` environment variable:
187+
188+
```bash
189+
export SOLANA_KEYPAIR=/path/to/keypair/id.json
116190
```
191+
192+
If you do not have a Solana keypair, you can generate one by downloading and installing the [Solana CLI](https://docs.solanalabs.com/cli/install), the running `solana-keygen new`.

0 commit comments

Comments
 (0)