Skip to content

Commit

Permalink
pull oracle support
Browse files Browse the repository at this point in the history
  • Loading branch information
0xodia committed Jul 17, 2024
1 parent 8f9454a commit 3e68135
Show file tree
Hide file tree
Showing 5 changed files with 564 additions and 51 deletions.
191 changes: 153 additions & 38 deletions solend-sdk/__tests__/obligation.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,165 @@
import {
ComputeBudgetProgram,
Connection,
Keypair,
PublicKey,
TransactionMessage,
VersionedTransaction,
} from "@solana/web3.js";
import { parseObligation } from "../src";
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
import { PythSolanaReceiver, pythSolanaReceiverIdl } from "@pythnetwork/pyth-solana-receiver";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { AnchorProvider, Program } from "@coral-xyz/anchor-30";
import { CrossbarClient, loadLookupTables, PullFeed, SB_ON_DEMAND_PID } from "@switchboard-xyz/on-demand";

jest.setTimeout(50_000);

describe("check", function () {
it("parses obligation in both formats", async function () {
const zstdEncodedObligationData = Buffer.from(
"KLUv/QBYLQoAdBEBWZHIBwAAAAABM7MexO/4+iia6oyVTAFjLi12SQjOVE1oZb3vERv/YSsChcZ+Ktz1mRBrkyRk1BwBK+MRLDs2kN6sQJrkE/068ae2QKj1GoAIagEAUiLO4HOtZQD9iDA+OBRghg/ZNGo1t+NsujMBAQFsp+C1qN6toMtzc12/wVf8DODwcnh4K7b1j6DEElE6V8esAGvgw8KAVqMAbcvwdUngHTQhP3KK2EcTtbcXhy+sOBPKuWOvMSyN283/9ZTFWnxCDgDvyiK06ZHhKd04ANAh+wRuI+ENjqkOBTFIeS9ID9NTKr93sO+769EA4idmIqc8x1Enq7K/tz3DwaHWY5RMvzQOIJZ5G4lBjXXPHRJX7vzRwA8SAM1yFQDEt5APgChdqyVY2VtAAS80LATg+oCsBCUWCFQgLRhoqHwAcXWBMw==",
"base64"
);
const base64EncodedObligationData = Buffer.from(
"AVmRyAcAAAAAATOzHsTv+PoomuqMlUwBYy4tdkkIzlRNaGW97xEb/2ErAoXGfirc9ZkQa5MkZNQcASvjESw7NpDerECa5BP9OvGntkCo9RqACGoBAAAAAAAAUiLO4HOtZQAAAAAAAAAAAP2IMD44FGCGDwEAAAAAAADZNGo1t+NsujMBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBbKfgtajeraDLc3Ndv8FX/Azg8HJ4eCu29Y+gxBJROlfHrAAAAAAAAGvgw8KAVqMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG3L8HVJ4B00IT9yithHE7W3F4cvrDgTyrljrzEsjdvN//WUxVp8Qg4AAAAAAAAAAO/KIrTpkeEp3TgAAAAAAABSIs7gc61lAAAAAAAAAAAAAAAAAAAAAABty/B1SeAdNCE/corYRxO1txeHL6w4E8q5Y68xLI3bzf/1lMVafEIOAAAAAAAAAADvyiK06ZHhKd04AAAAAAAAUiLO4HOtZQAAAAAAAAAAANAh+wQAAAAAbiPhDY6pDgUxAQAAAAAAAAAAAAAAAAAASHkvSA/TUyq/d7Dvu+vRAOInZiKnPMdRJ6uyv7c9w8Gh1mOUTL80DgAAAAAAAAAAIJZ5G4lBjXXPIfsEAAAAAB0SV+780cAPMQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"base64"
);

const obligationPubkey = new PublicKey(
"FfRLBU1gHm3MyqJ3KX6dBnsxxJtwVGCwKFwJrfDnceWN"
);
const connection = new Connection("https://api.mainnet-beta.solana.com", {
commitment: "finalized",
});

const zstdEncodedObligation = await connection.getAccountInfo(
obligationPubkey
);
zstdEncodedObligation!.data = zstdEncodedObligationData;
const base64EncodedObligation = await connection.getAccountInfo(
obligationPubkey
);
base64EncodedObligation!.data = base64EncodedObligationData;

const parsedzstdEncodedObligation = parseObligation(
obligationPubkey,
zstdEncodedObligation!,
"base64+zstd"
);
const parsedbase64EncodedObligation = parseObligation(
obligationPubkey,
base64EncodedObligation!
);

expect(parsedzstdEncodedObligation!).toMatchObject(
parsedbase64EncodedObligation!
);
const connection = new Connection("https://solendf-solendf-67c7.rpcpool.com/6096fc4b-78fc-4130-a42a-e6d4b9c37813");
// const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network");
// const pythSolanaReceiver = new PythSolanaReceiver({
// connection: connection,
// wallet: new NodeWallet(Keypair.fromSeed(new Uint8Array(32).fill(1)))
// });
// const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
// closeUpdateAccounts: true,
// });

const provider = new AnchorProvider(connection, new NodeWallet(Keypair.fromSecretKey(new Uint8Array(
[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]
))), {});
const idl = (await Program.fetchIdl(SB_ON_DEMAND_PID, provider))!;
const sbod = new Program(idl, provider);

const sbPulledOracles = [
'2F9M59yYc28WMrAymNWceaBEk8ZmDAjUAKULp8seAJF3',
'AZcoqpWhMJUaKEDUfKsfzCr3Y96gSQwv43KSQ6KpeyQ1'
];
const feedAccounts = sbPulledOracles.map((oracleKey) => new PullFeed(sbod as any, oracleKey));
const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz");

// Responses is Array<[pullIx, responses, success]>
const responses = await Promise.all(feedAccounts.map((feedAccount) => feedAccount.fetchUpdateIx({ numSignatures: 1, crossbarClient: crossbar })));
const oracles = responses.flatMap((x) => x[1].map(y => y.oracle));
const lookupTables = await loadLookupTables([...oracles, ...feedAccounts]);

console.log(responses);

// Get the latest context
const {
value: { blockhash },
} = await connection.getLatestBlockhashAndContext();

// Get Transaction Message
const message = new TransactionMessage({
payerKey: provider.publicKey,
recentBlockhash: blockhash,
instructions: [...responses.map(r => r[0]!)],
}).compileToV0Message(lookupTables);

// Get Versioned Transaction
const vtx = new VersionedTransaction(message);
provider.wallet.signAllTransactions([vtx]);
const sig = await connection.sendRawTransaction(vtx.serialize(), {skipPreflight: true});
await connection.confirmTransaction(sig, 'confirmed');
// let priceFeedUpdateData;
// priceFeedUpdateData = await priceServiceConnection.getLatestVaas(
// [
// 'ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d',
// '67be9f519b95cf24338801051f9a808eff0a578ccb388db73b7f6fe1de019ffb',
// 'c2289a6a43d2ce91c6f55caec370f4acc38a2ed477f58813334c6d03749ff2a4',
// '89875379e70f8fbadc17aef315adf3a8d5d160b811435537e03c97e8aac97d9c',
// '72b021217ca3fe68922a19aaf990109cb9d84e9ad004b4d2025ad6f529314419',
// 'eff7446475e218517566ea99e72a4abec2e1bd8498b43b7d8331e29dcb059389'
// ]
// );

// await transactionBuilder.addUpdatePriceFeed(
// priceFeedUpdateData,
// 0 // shardId of 0
// );
// await transactionBuilder.addPostPriceUpdates(priceFeedUpdateData);

// const transactionsWithSigners = await transactionBuilder.buildVersionedTransactions({
// tightComputeBudget: true,
// });

// console.log(transactionsWithSigners);
// console.log(transactionsWithSigners.map(t => t.tx.message.compiledInstructions));


});

// it("parses obligation in both formats", async function () {
// const connection = new Connection("https://solendf-solendf-67c7.rpcpool.com/6096fc4b-78fc-4130-a42a-e6d4b9c37813");
// const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network");
// const pythSolanaReceiver = new PythSolanaReceiver({
// connection: connection,
// wallet: new NodeWallet(Keypair.fromSecretKey(new Uint8Array(
// [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]
// )))
// });
// const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
// closeUpdateAccounts: true,
// });

// const oracleAccount = await connection.getAccountInfo(new PublicKey('FFv5yoCGhEgWv6mXhwv4KX8A2dYcVAzi88a6Yu8Tf3iB'));

// console.log(oracleAccount, pythSolanaReceiverIdl);
// const priceUpdate = pythSolanaReceiver.receiver.account.priceUpdateV2.coder.accounts.decode(
// 'priceUpdateV2',
// oracleAccount!.data,
// );
// const exponent = 10 ** priceUpdate.priceMessage.exponent;
// const spotPrice = priceUpdate.priceMessage.price.toNumber() * exponent;
// const emaPrice = priceUpdate.priceMessage.emaPrice.toNumber() * exponent;
// console.log(spotPrice, emaPrice)
// const priceFeedId = priceUpdate.priceMessage.feedId;

// let priceFeedUpdateData;
// priceFeedUpdateData = await priceServiceConnection.getLatestVaas(
// [
// '0x93c3def9b169f49eed14c9d73ed0e942c666cf0e1290657ec82038ebb792c2a8', // BLZE
// '0xf2fc1dfcf51867abfa70874c929e920edc649e4997cbac88f280094df8c72bcd', // EUROE
// ]
// );

// console.log(priceFeedUpdateData);
// await transactionBuilder.addUpdatePriceFeed(
// priceFeedUpdateData,
// 0 // shardId of 0
// );
// // await transactionBuilder.addPostPriceUpdates(priceFeedUpdateData);

// const transactionsWithSigners = await transactionBuilder.buildVersionedTransactions({
// tightComputeBudget: true,
// });

// // transactionBuilder.addPriceConsumerInstructions


// console.log(transactionsWithSigners);
// const pullPriceTxns = [] as Array<VersionedTransaction>;
// // TODO: Verify if there even is signers
// for (const transaction of transactionsWithSigners) {
// const signers = transaction.signers;
// let tx = transaction.tx;

// if (signers) {
// tx.sign(signers);
// pullPriceTxns.push(tx);
// }
// }

// pythSolanaReceiver.wallet.signAllTransactions(pullPriceTxns)

// for (const tx of pullPriceTxns) {
// const serializedTransaction = tx.serialize();
// const sig = await connection.sendRawTransaction(serializedTransaction, {skipPreflight: true});
// await connection.confirmTransaction(sig, 'confirmed');
// }

// });
});
120 changes: 120 additions & 0 deletions solend-sdk/__tests__/oracle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {
ComputeBudgetProgram,
Connection,
Keypair,
PublicKey,
TransactionMessage,
VersionedTransaction,
} from "@solana/web3.js";
import { parseObligation } from "../src";
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
import { PythSolanaReceiver, pythSolanaReceiverIdl } from "@pythnetwork/pyth-solana-receiver";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { AnchorProvider, Program } from "@coral-xyz/anchor-30";
import { CrossbarClient, loadLookupTables, PullFeed, SB_ON_DEMAND_PID } from "@switchboard-xyz/on-demand";

jest.setTimeout(50_000);

describe("check", function () {
it("parses obligation in both formats", async function () {
const connection = new Connection("https://api.mainnet-beta.solana.com");
const testKey = []
if (testKey.length === 0) {
throw Error('Best tested with a throwaway mainnet test account.')
}

const provider = new AnchorProvider(connection, new NodeWallet(Keypair.fromSecretKey(new Uint8Array(
testKey
))), {});
const idl = (await Program.fetchIdl(SB_ON_DEMAND_PID, provider))!;
const sbod = new Program(idl, provider);

const sbPulledOracles = [
'2F9M59yYc28WMrAymNWceaBEk8ZmDAjUAKULp8seAJF3',
'AZcoqpWhMJUaKEDUfKsfzCr3Y96gSQwv43KSQ6KpeyQ1'
];

const feedAccounts = sbPulledOracles.map((oracleKey) => new PullFeed(sbod as any, oracleKey));
const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz");

// Responses is Array<[pullIx, responses, success]>
const responses = await Promise.all(feedAccounts.map((feedAccount) => feedAccount.fetchUpdateIx({ numSignatures: 1, crossbarClient: crossbar })));
const oracles = responses.flatMap((x) => x[1].map(y => y.oracle));
const lookupTables = await loadLookupTables([...oracles, ...feedAccounts]);

// Get the latest context
const {
value: { blockhash },
} = await connection.getLatestBlockhashAndContext();

// Get Transaction Message
const message = new TransactionMessage({
payerKey: provider.publicKey,
recentBlockhash: blockhash,
instructions: [...responses.map(r => r[0]!)],
}).compileToV0Message(lookupTables);

// Get Versioned Transaction
const vtx = new VersionedTransaction(message);
provider.wallet.signAllTransactions([vtx]);
const sig = await connection.sendRawTransaction(vtx.serialize(), {skipPreflight: true});
await connection.confirmTransaction(sig, 'confirmed');
});

it("parses obligation in both formats", async function () {
const connection = new Connection("https://api.mainnet-beta.solana.com");
const testKey = []
if (testKey.length === 0) {
throw Error('Best tested with a throwaway mainnet test account.')
}
const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network");
const pythSolanaReceiver = new PythSolanaReceiver({
connection: connection,
wallet: new NodeWallet(Keypair.fromSecretKey(new Uint8Array(
testKey
)))
});
const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
closeUpdateAccounts: true,
});

let priceFeedUpdateData;
priceFeedUpdateData = await priceServiceConnection.getLatestVaas(
[
'0x93c3def9b169f49eed14c9d73ed0e942c666cf0e1290657ec82038ebb792c2a8', // BLZE
'0xf2fc1dfcf51867abfa70874c929e920edc649e4997cbac88f280094df8c72bcd', // EUROE
]
);

await transactionBuilder.addUpdatePriceFeed(
priceFeedUpdateData,
0 // shardId of 0
);

const transactionsWithSigners = await transactionBuilder.buildVersionedTransactions({
tightComputeBudget: true,
});

const pullPriceTxns = [] as Array<VersionedTransaction>;

for (const transaction of transactionsWithSigners) {
const signers = transaction.signers;
let tx = transaction.tx;

if (signers) {
tx.sign(signers);
pullPriceTxns.push(tx);
}
}

pythSolanaReceiver.wallet.signAllTransactions(pullPriceTxns)

for (const tx of pullPriceTxns) {
const serializedTransaction = tx.serialize();
const sig = await connection.sendRawTransaction(serializedTransaction, {skipPreflight: true});
await connection.confirmTransaction(sig, 'confirmed');
}

});
});

5 changes: 4 additions & 1 deletion solend-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solendprotocol/solend-sdk",
"version": "0.10.9",
"version": "0.10.11",
"private": true,
"main": "src/index.ts",
"module": "src/index.ts",
Expand All @@ -21,10 +21,13 @@
"dependencies": {
"@project-serum/anchor": "^0.24.2",
"@pythnetwork/client": "^2.12.0",
"@pythnetwork/price-service-client": "^1.9.0",
"@pythnetwork/pyth-solana-receiver": "^0.8.0",
"@solana/buffer-layout": "=4.0.1",
"@solana/spl-token": "^0.3.7",
"@solana/web3.js": "=1.92.3",
"@solflare-wallet/utl-sdk": "^1.4.0",
"@switchboard-xyz/on-demand": "^1.1.39",
"@switchboard-xyz/sbv2-lite": "^0.2.4",
"axios": "^0.24.0",
"bignumber.js": "^9.0.2",
Expand Down
Loading

0 comments on commit 3e68135

Please sign in to comment.