Skip to content

Commit ce2ab8d

Browse files
committed
pull oracle support
1 parent 8f9454a commit ce2ab8d

5 files changed

Lines changed: 568 additions & 52 deletions

File tree

Lines changed: 153 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,165 @@
11
import {
2+
ComputeBudgetProgram,
23
Connection,
4+
Keypair,
35
PublicKey,
6+
TransactionMessage,
7+
VersionedTransaction,
48
} from "@solana/web3.js";
59
import { parseObligation } from "../src";
10+
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
11+
import { PythSolanaReceiver, pythSolanaReceiverIdl } from "@pythnetwork/pyth-solana-receiver";
12+
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
13+
import { AnchorProvider, Program } from "@coral-xyz/anchor-30";
14+
import { CrossbarClient, loadLookupTables, PullFeed, SB_ON_DEMAND_PID } from "@switchboard-xyz/on-demand";
615

716
jest.setTimeout(50_000);
817

918
describe("check", function () {
1019
it("parses obligation in both formats", async function () {
11-
const zstdEncodedObligationData = Buffer.from(
12-
"KLUv/QBYLQoAdBEBWZHIBwAAAAABM7MexO/4+iia6oyVTAFjLi12SQjOVE1oZb3vERv/YSsChcZ+Ktz1mRBrkyRk1BwBK+MRLDs2kN6sQJrkE/068ae2QKj1GoAIagEAUiLO4HOtZQD9iDA+OBRghg/ZNGo1t+NsujMBAQFsp+C1qN6toMtzc12/wVf8DODwcnh4K7b1j6DEElE6V8esAGvgw8KAVqMAbcvwdUngHTQhP3KK2EcTtbcXhy+sOBPKuWOvMSyN283/9ZTFWnxCDgDvyiK06ZHhKd04ANAh+wRuI+ENjqkOBTFIeS9ID9NTKr93sO+769EA4idmIqc8x1Enq7K/tz3DwaHWY5RMvzQOIJZ5G4lBjXXPHRJX7vzRwA8SAM1yFQDEt5APgChdqyVY2VtAAS80LATg+oCsBCUWCFQgLRhoqHwAcXWBMw==",
13-
"base64"
14-
);
15-
const base64EncodedObligationData = Buffer.from(
16-
"AVmRyAcAAAAAATOzHsTv+PoomuqMlUwBYy4tdkkIzlRNaGW97xEb/2ErAoXGfirc9ZkQa5MkZNQcASvjESw7NpDerECa5BP9OvGntkCo9RqACGoBAAAAAAAAUiLO4HOtZQAAAAAAAAAAAP2IMD44FGCGDwEAAAAAAADZNGo1t+NsujMBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBbKfgtajeraDLc3Ndv8FX/Azg8HJ4eCu29Y+gxBJROlfHrAAAAAAAAGvgw8KAVqMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG3L8HVJ4B00IT9yithHE7W3F4cvrDgTyrljrzEsjdvN//WUxVp8Qg4AAAAAAAAAAO/KIrTpkeEp3TgAAAAAAABSIs7gc61lAAAAAAAAAAAAAAAAAAAAAABty/B1SeAdNCE/corYRxO1txeHL6w4E8q5Y68xLI3bzf/1lMVafEIOAAAAAAAAAADvyiK06ZHhKd04AAAAAAAAUiLO4HOtZQAAAAAAAAAAANAh+wQAAAAAbiPhDY6pDgUxAQAAAAAAAAAAAAAAAAAASHkvSA/TUyq/d7Dvu+vRAOInZiKnPMdRJ6uyv7c9w8Gh1mOUTL80DgAAAAAAAAAAIJZ5G4lBjXXPIfsEAAAAAB0SV+780cAPMQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
17-
"base64"
18-
);
19-
20-
const obligationPubkey = new PublicKey(
21-
"FfRLBU1gHm3MyqJ3KX6dBnsxxJtwVGCwKFwJrfDnceWN"
22-
);
23-
const connection = new Connection("https://api.mainnet-beta.solana.com", {
24-
commitment: "finalized",
25-
});
26-
27-
const zstdEncodedObligation = await connection.getAccountInfo(
28-
obligationPubkey
29-
);
30-
zstdEncodedObligation!.data = zstdEncodedObligationData;
31-
const base64EncodedObligation = await connection.getAccountInfo(
32-
obligationPubkey
33-
);
34-
base64EncodedObligation!.data = base64EncodedObligationData;
35-
36-
const parsedzstdEncodedObligation = parseObligation(
37-
obligationPubkey,
38-
zstdEncodedObligation!,
39-
"base64+zstd"
40-
);
41-
const parsedbase64EncodedObligation = parseObligation(
42-
obligationPubkey,
43-
base64EncodedObligation!
44-
);
45-
46-
expect(parsedzstdEncodedObligation!).toMatchObject(
47-
parsedbase64EncodedObligation!
48-
);
20+
const connection = new Connection("https://solendf-solendf-67c7.rpcpool.com/6096fc4b-78fc-4130-a42a-e6d4b9c37813");
21+
// const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network");
22+
// const pythSolanaReceiver = new PythSolanaReceiver({
23+
// connection: connection,
24+
// wallet: new NodeWallet(Keypair.fromSeed(new Uint8Array(32).fill(1)))
25+
// });
26+
// const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
27+
// closeUpdateAccounts: true,
28+
// });
29+
30+
const provider = new AnchorProvider(connection, new NodeWallet(Keypair.fromSecretKey(new Uint8Array(
31+
[103,129,37,223,47,99,63,95,41,95,190,254,216,11,20,217,101,217,65,173,210,207,137,44,176,136,93,84,15,87,212,78,254,157,169,160,244,214,188,162,227,202,121,33,107,191,169,212,16,93,176,86,248,238,250,64,147,23,50,212,180,223,6,63]
32+
))), {});
33+
const idl = (await Program.fetchIdl(SB_ON_DEMAND_PID, provider))!;
34+
const sbod = new Program(idl, provider);
35+
36+
const sbPulledOracles = [
37+
'2F9M59yYc28WMrAymNWceaBEk8ZmDAjUAKULp8seAJF3',
38+
'AZcoqpWhMJUaKEDUfKsfzCr3Y96gSQwv43KSQ6KpeyQ1'
39+
];
40+
const feedAccounts = sbPulledOracles.map((oracleKey) => new PullFeed(sbod as any, oracleKey));
41+
const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz");
42+
43+
// Responses is Array<[pullIx, responses, success]>
44+
const responses = await Promise.all(feedAccounts.map((feedAccount) => feedAccount.fetchUpdateIx({ numSignatures: 1, crossbarClient: crossbar })));
45+
const oracles = responses.flatMap((x) => x[1].map(y => y.oracle));
46+
const lookupTables = await loadLookupTables([...oracles, ...feedAccounts]);
47+
48+
console.log(responses);
49+
50+
// Get the latest context
51+
const {
52+
value: { blockhash },
53+
} = await connection.getLatestBlockhashAndContext();
54+
55+
// Get Transaction Message
56+
const message = new TransactionMessage({
57+
payerKey: provider.publicKey,
58+
recentBlockhash: blockhash,
59+
instructions: [...responses.map(r => r[0]!)],
60+
}).compileToV0Message(lookupTables);
61+
62+
// Get Versioned Transaction
63+
const vtx = new VersionedTransaction(message);
64+
provider.wallet.signAllTransactions([vtx]);
65+
const sig = await connection.sendRawTransaction(vtx.serialize(), {skipPreflight: true});
66+
await connection.confirmTransaction(sig, 'confirmed');
67+
// let priceFeedUpdateData;
68+
// priceFeedUpdateData = await priceServiceConnection.getLatestVaas(
69+
// [
70+
// 'ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d',
71+
// '67be9f519b95cf24338801051f9a808eff0a578ccb388db73b7f6fe1de019ffb',
72+
// 'c2289a6a43d2ce91c6f55caec370f4acc38a2ed477f58813334c6d03749ff2a4',
73+
// '89875379e70f8fbadc17aef315adf3a8d5d160b811435537e03c97e8aac97d9c',
74+
// '72b021217ca3fe68922a19aaf990109cb9d84e9ad004b4d2025ad6f529314419',
75+
// 'eff7446475e218517566ea99e72a4abec2e1bd8498b43b7d8331e29dcb059389'
76+
// ]
77+
// );
78+
79+
// await transactionBuilder.addUpdatePriceFeed(
80+
// priceFeedUpdateData,
81+
// 0 // shardId of 0
82+
// );
83+
// await transactionBuilder.addPostPriceUpdates(priceFeedUpdateData);
84+
85+
// const transactionsWithSigners = await transactionBuilder.buildVersionedTransactions({
86+
// tightComputeBudget: true,
87+
// });
88+
89+
// console.log(transactionsWithSigners);
90+
// console.log(transactionsWithSigners.map(t => t.tx.message.compiledInstructions));
91+
92+
4993
});
94+
95+
// it("parses obligation in both formats", async function () {
96+
// const connection = new Connection("https://solendf-solendf-67c7.rpcpool.com/6096fc4b-78fc-4130-a42a-e6d4b9c37813");
97+
// const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network");
98+
// const pythSolanaReceiver = new PythSolanaReceiver({
99+
// connection: connection,
100+
// wallet: new NodeWallet(Keypair.fromSecretKey(new Uint8Array(
101+
// [103,129,37,223,47,99,63,95,41,95,190,254,216,11,20,217,101,217,65,173,210,207,137,44,176,136,93,84,15,87,212,78,254,157,169,160,244,214,188,162,227,202,121,33,107,191,169,212,16,93,176,86,248,238,250,64,147,23,50,212,180,223,6,63]
102+
// )))
103+
// });
104+
// const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
105+
// closeUpdateAccounts: true,
106+
// });
107+
108+
// const oracleAccount = await connection.getAccountInfo(new PublicKey('FFv5yoCGhEgWv6mXhwv4KX8A2dYcVAzi88a6Yu8Tf3iB'));
109+
110+
// console.log(oracleAccount, pythSolanaReceiverIdl);
111+
// const priceUpdate = pythSolanaReceiver.receiver.account.priceUpdateV2.coder.accounts.decode(
112+
// 'priceUpdateV2',
113+
// oracleAccount!.data,
114+
// );
115+
// const exponent = 10 ** priceUpdate.priceMessage.exponent;
116+
// const spotPrice = priceUpdate.priceMessage.price.toNumber() * exponent;
117+
// const emaPrice = priceUpdate.priceMessage.emaPrice.toNumber() * exponent;
118+
// console.log(spotPrice, emaPrice)
119+
// const priceFeedId = priceUpdate.priceMessage.feedId;
120+
121+
// let priceFeedUpdateData;
122+
// priceFeedUpdateData = await priceServiceConnection.getLatestVaas(
123+
// [
124+
// '0x93c3def9b169f49eed14c9d73ed0e942c666cf0e1290657ec82038ebb792c2a8', // BLZE
125+
// '0xf2fc1dfcf51867abfa70874c929e920edc649e4997cbac88f280094df8c72bcd', // EUROE
126+
// ]
127+
// );
128+
129+
// console.log(priceFeedUpdateData);
130+
// await transactionBuilder.addUpdatePriceFeed(
131+
// priceFeedUpdateData,
132+
// 0 // shardId of 0
133+
// );
134+
// // await transactionBuilder.addPostPriceUpdates(priceFeedUpdateData);
135+
136+
// const transactionsWithSigners = await transactionBuilder.buildVersionedTransactions({
137+
// tightComputeBudget: true,
138+
// });
139+
140+
// // transactionBuilder.addPriceConsumerInstructions
141+
142+
143+
// console.log(transactionsWithSigners);
144+
// const pullPriceTxns = [] as Array<VersionedTransaction>;
145+
// // TODO: Verify if there even is signers
146+
// for (const transaction of transactionsWithSigners) {
147+
// const signers = transaction.signers;
148+
// let tx = transaction.tx;
149+
150+
// if (signers) {
151+
// tx.sign(signers);
152+
// pullPriceTxns.push(tx);
153+
// }
154+
// }
155+
156+
// pythSolanaReceiver.wallet.signAllTransactions(pullPriceTxns)
157+
158+
// for (const tx of pullPriceTxns) {
159+
// const serializedTransaction = tx.serialize();
160+
// const sig = await connection.sendRawTransaction(serializedTransaction, {skipPreflight: true});
161+
// await connection.confirmTransaction(sig, 'confirmed');
162+
// }
163+
164+
// });
50165
});
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import {
2+
ComputeBudgetProgram,
3+
Connection,
4+
Keypair,
5+
PublicKey,
6+
TransactionMessage,
7+
VersionedTransaction,
8+
} from "@solana/web3.js";
9+
import { parseObligation } from "../src";
10+
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
11+
import { PythSolanaReceiver, pythSolanaReceiverIdl } from "@pythnetwork/pyth-solana-receiver";
12+
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
13+
import { AnchorProvider, Program } from "@coral-xyz/anchor-30";
14+
import { CrossbarClient, loadLookupTables, PullFeed, SB_ON_DEMAND_PID } from "@switchboard-xyz/on-demand";
15+
16+
jest.setTimeout(50_000);
17+
18+
describe("check", function () {
19+
it("pulls switchboard oracle", async function () {
20+
const connection = new Connection("https://api.mainnet-beta.solana.com");
21+
const testKey = []
22+
if (testKey.length === 0) {
23+
throw Error('Best tested with a throwaway mainnet test account.')
24+
}
25+
26+
const provider = new AnchorProvider(connection, new NodeWallet(Keypair.fromSecretKey(new Uint8Array(
27+
testKey
28+
))), {});
29+
const idl = (await Program.fetchIdl(SB_ON_DEMAND_PID, provider))!;
30+
const sbod = new Program(idl, provider);
31+
32+
const sbPulledOracles = [
33+
'2F9M59yYc28WMrAymNWceaBEk8ZmDAjUAKULp8seAJF3',
34+
'AZcoqpWhMJUaKEDUfKsfzCr3Y96gSQwv43KSQ6KpeyQ1'
35+
];
36+
37+
const feedAccounts = sbPulledOracles.map((oracleKey) => new PullFeed(sbod as any, oracleKey));
38+
const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz");
39+
40+
// Responses is Array<[pullIx, responses, success]>
41+
const responses = await Promise.all(feedAccounts.map((feedAccount) => feedAccount.fetchUpdateIx({ numSignatures: 1, crossbarClient: crossbar })));
42+
const oracles = responses.flatMap((x) => x[1].map(y => y.oracle));
43+
const lookupTables = await loadLookupTables([...oracles, ...feedAccounts]);
44+
45+
// Get the latest context
46+
const {
47+
value: { blockhash },
48+
} = await connection.getLatestBlockhashAndContext();
49+
50+
// Get Transaction Message
51+
const message = new TransactionMessage({
52+
payerKey: provider.publicKey,
53+
recentBlockhash: blockhash,
54+
instructions: [...responses.map(r => r[0]!)],
55+
}).compileToV0Message(lookupTables);
56+
57+
// Get Versioned Transaction
58+
const vtx = new VersionedTransaction(message);
59+
provider.wallet.signAllTransactions([vtx]);
60+
const sig = await connection.sendRawTransaction(vtx.serialize(), {skipPreflight: true});
61+
await connection.confirmTransaction(sig, 'confirmed');
62+
});
63+
64+
it("pulls pyth oracles", async function () {
65+
const connection = new Connection("https://api.mainnet-beta.solana.com");
66+
const testKey = []
67+
if (testKey.length === 0) {
68+
throw Error('Best tested with a throwaway mainnet test account.')
69+
}
70+
const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network");
71+
const pythSolanaReceiver = new PythSolanaReceiver({
72+
connection: connection,
73+
wallet: new NodeWallet(Keypair.fromSecretKey(new Uint8Array(
74+
testKey
75+
)))
76+
});
77+
const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
78+
closeUpdateAccounts: true,
79+
});
80+
81+
let priceFeedUpdateData;
82+
priceFeedUpdateData = await priceServiceConnection.getLatestVaas(
83+
[
84+
'0x93c3def9b169f49eed14c9d73ed0e942c666cf0e1290657ec82038ebb792c2a8', // BLZE
85+
'0xf2fc1dfcf51867abfa70874c929e920edc649e4997cbac88f280094df8c72bcd', // EUROE
86+
]
87+
);
88+
89+
await transactionBuilder.addUpdatePriceFeed(
90+
priceFeedUpdateData,
91+
0 // shardId of 0
92+
);
93+
94+
const transactionsWithSigners = await transactionBuilder.buildVersionedTransactions({
95+
tightComputeBudget: true,
96+
});
97+
98+
const pullPriceTxns = [] as Array<VersionedTransaction>;
99+
100+
for (const transaction of transactionsWithSigners) {
101+
const signers = transaction.signers;
102+
let tx = transaction.tx;
103+
104+
if (signers) {
105+
tx.sign(signers);
106+
pullPriceTxns.push(tx);
107+
}
108+
}
109+
110+
pythSolanaReceiver.wallet.signAllTransactions(pullPriceTxns)
111+
112+
for (const tx of pullPriceTxns) {
113+
const serializedTransaction = tx.serialize();
114+
const sig = await connection.sendRawTransaction(serializedTransaction, {skipPreflight: true});
115+
await connection.confirmTransaction(sig, 'confirmed');
116+
}
117+
118+
});
119+
});
120+

solend-sdk/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@solendprotocol/solend-sdk",
3-
"version": "0.10.9",
3+
"version": "0.10.11",
44
"private": true,
55
"main": "src/index.ts",
66
"module": "src/index.ts",
@@ -21,10 +21,13 @@
2121
"dependencies": {
2222
"@project-serum/anchor": "^0.24.2",
2323
"@pythnetwork/client": "^2.12.0",
24+
"@pythnetwork/price-service-client": "^1.9.0",
25+
"@pythnetwork/pyth-solana-receiver": "^0.8.0",
2426
"@solana/buffer-layout": "=4.0.1",
2527
"@solana/spl-token": "^0.3.7",
2628
"@solana/web3.js": "=1.92.3",
2729
"@solflare-wallet/utl-sdk": "^1.4.0",
30+
"@switchboard-xyz/on-demand": "^1.1.39",
2831
"@switchboard-xyz/sbv2-lite": "^0.2.4",
2932
"axios": "^0.24.0",
3033
"bignumber.js": "^9.0.2",

0 commit comments

Comments
 (0)