diff --git a/solend-sdk/__tests__/oracle.test.ts b/solend-sdk/__tests__/oracle.test.ts index 14235e08..a0e1cf59 100644 --- a/solend-sdk/__tests__/oracle.test.ts +++ b/solend-sdk/__tests__/oracle.test.ts @@ -3,6 +3,7 @@ import { Connection, Keypair, PublicKey, + SystemProgram, TransactionMessage, VersionedTransaction, } from "@solana/web3.js"; @@ -12,13 +13,15 @@ import { 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"; +import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; jest.setTimeout(50_000); - describe("check", function () { + const connection = new Connection("https://api.mainnet-beta.solana.com"); + const testKey = []; + + describe("pulls sb oracles", function () { it("pulls switchboard oracle", 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.') } @@ -31,14 +34,20 @@ import { const sbPulledOracles = [ '2F9M59yYc28WMrAymNWceaBEk8ZmDAjUAKULp8seAJF3', - 'AZcoqpWhMJUaKEDUfKsfzCr3Y96gSQwv43KSQ6KpeyQ1' + 'AZcoqpWhMJUaKEDUfKsfzCr3Y96gSQwv43KSQ6KpeyQ1', + 'Ai2GsLRioGKwVgWX8dtbLF5rJJEZX17SteGEDqrpzBv3', + '4sPZ75ipUH9W2CC7gfpipLpPLN5m7RD2FES9SfegfZbP', + 'AJkAFiXdbMonys8rTXZBrRnuUiLcDFdkyoPuvrVKXhex', + // '65J9bVEMhNbtbsNgArNV1K4krzcsomjho4bgR51sZXoj' ]; + // Example usage const feedAccounts = sbPulledOracles.map((oracleKey) => new PullFeed(sbod as any, oracleKey)); - const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz"); + const crossbar = new CrossbarClient("https://crossbar-fvumormova-uc.a.run.app"); // Responses is Array<[pullIx, responses, success]> - const responses = await Promise.all(feedAccounts.map((feedAccount) => feedAccount.fetchUpdateIx({ numSignatures: 1, crossbarClient: crossbar }))); + const responses = await Promise.all(feedAccounts.map((feedAccount) => feedAccount.fetchUpdateIx({ numSignatures: 1, crossbarClient: crossbar, + gateway: 'https://xoracle-1-mn.switchboard.xyz' }))); const oracles = responses.flatMap((x) => x[1].map(y => y.oracle)); const lookupTables = await loadLookupTables([...oracles, ...feedAccounts]); @@ -46,7 +55,7 @@ import { const { value: { blockhash }, } = await connection.getLatestBlockhashAndContext(); - + // Get Transaction Message const message = new TransactionMessage({ payerKey: provider.publicKey, @@ -62,50 +71,48 @@ import { }); it("pulls pyth oracles", 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; + 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( + [ + 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a', // USDC + ] + ); + + await transactionBuilder.addUpdatePriceFeed( + priceFeedUpdateData, + 0 // shardId of 0 + ); + + const transactionsWithSigners = await transactionBuilder.buildVersionedTransactions({ + tightComputeBudget: true, + jitoTipLamports: 10 - for (const transaction of transactionsWithSigners) { + }); + + const pullPriceTxns = [] as Array; + + for (const transaction of transactionsWithSigners) { const signers = transaction.signers; let tx = transaction.tx; - - if (signers) { + if (signers) { tx.sign(signers); + } pullPriceTxns.push(tx); - } - } + } pythSolanaReceiver.wallet.signAllTransactions(pullPriceTxns) diff --git a/solend-sdk/package.json b/solend-sdk/package.json index a02d9aef..97221649 100644 --- a/solend-sdk/package.json +++ b/solend-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@solendprotocol/solend-sdk", - "version": "0.10.11", + "version": "0.11.10", "private": true, "main": "src/index.ts", "module": "src/index.ts", @@ -19,6 +19,7 @@ "release": "yarn build; ts-node ./prepublish.ts; yarn --cwd ./dist publish" }, "dependencies": { + "@coral-xyz/anchor-30": "npm:@coral-xyz/anchor@0.30.1", "@project-serum/anchor": "^0.24.2", "@pythnetwork/client": "^2.12.0", "@pythnetwork/price-service-client": "^1.9.0", diff --git a/solend-sdk/src/core/actions.ts b/solend-sdk/src/core/actions.ts index 39f78058..22692848 100644 --- a/solend-sdk/src/core/actions.ts +++ b/solend-sdk/src/core/actions.ts @@ -3,7 +3,6 @@ import { BlockhashWithExpiryBlockHeight, ComputeBudgetProgram, Connection, - Keypair, PublicKey, SystemProgram, Transaction, @@ -48,7 +47,6 @@ import { import { NULL_ORACLE, POSITION_LIMIT } from "./constants"; import { EnvironmentType, PoolType, ReserveType } from "./types"; import { getProgramId, U64_MAX, WAD } from "./constants"; -import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; import { PriceServiceConnection } from "@pythnetwork/price-service-client"; import { AnchorProvider, Program } from "@coral-xyz/anchor-30"; import { @@ -150,7 +148,8 @@ export class SolendActionCore { borrowReserves: Array, hostAta?: PublicKey, lookupTableAccount?: AddressLookupTableAccount, - ownerPublicKey?: PublicKey + ownerPublicKey?: PublicKey, + tipAmount?: number ) { this.programId = programId; this.connection = connection; @@ -175,7 +174,7 @@ export class SolendActionCore { this.borrowReserves = borrowReserves; this.lookupTableAccount = lookupTableAccount; this.ownerPublicKey = ownerPublicKey; - this.jitoTipAmount = 1000; + this.jitoTipAmount = tipAmount ?? 1000; this.wallet = wallet; } @@ -191,7 +190,8 @@ export class SolendActionCore { hostAta?: PublicKey, customObligationSeed?: string, lookupTableAddress?: PublicKey, - ownerPublicKey?: PublicKey + ownerPublicKey?: PublicKey, + tipAmount?: number ) { const seed = customObligationSeed ?? pool.address.slice(0, 32); const programId = getProgramId(environment); @@ -277,7 +277,8 @@ export class SolendActionCore { borrowReserves, hostAta, lookupTableAccount ?? undefined, - ownerPublicKey + ownerPublicKey, + tipAmount ); } @@ -351,7 +352,8 @@ export class SolendActionCore { environment: EnvironmentType = "production", customObligationAddress?: PublicKey, hostAta?: PublicKey, - lookupTableAddress?: PublicKey + lookupTableAddress?: PublicKey, + tipAmount?: number ) { const axn = await SolendActionCore.initialize( pool, @@ -364,7 +366,9 @@ export class SolendActionCore { customObligationAddress, hostAta, undefined, - lookupTableAddress + lookupTableAddress, + undefined, + tipAmount ); await axn.addSupportIxs("borrow"); @@ -493,7 +497,8 @@ export class SolendActionCore { environment: EnvironmentType = "production", obligationAddress?: PublicKey, obligationSeed?: string, - lookupTableAddress?: PublicKey + lookupTableAddress?: PublicKey, + tipAmount?: number ) { const axn = await SolendActionCore.initialize( pool, @@ -507,7 +512,8 @@ export class SolendActionCore { undefined, obligationSeed, lookupTableAddress, - undefined + undefined, + tipAmount ); await axn.addSupportIxs("withdraw"); @@ -597,8 +603,6 @@ export class SolendActionCore { } private getTipIx() { - if (!this.jitoTipAmount) return; - const tipAccounts = [ "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5", "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe", @@ -923,11 +927,7 @@ export class SolendActionCore { closeUpdateAccounts: true, }); - const provider = new AnchorProvider( - this.connection, - new NodeWallet(Keypair.fromSeed(new Uint8Array(32).fill(1))), - {} - ); + const provider = new AnchorProvider(this.connection, this.wallet, {}); const idl = (await Program.fetchIdl(SB_ON_DEMAND_PID, provider))!; const sbod = new Program(idl, provider); @@ -939,7 +939,9 @@ export class SolendActionCore { const feedAccounts = sbPulledOracles.map( (oracleKey) => new PullFeed(sbod as any, oracleKey) ); - const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz"); + const crossbar = new CrossbarClient( + "https://crossbar-fvumormova-uc.a.run.app" + ); // Responses is Array<[pullIx, responses, success]> const responses = await Promise.all( @@ -947,6 +949,7 @@ export class SolendActionCore { feedAccount.fetchUpdateIx({ numSignatures: 1, crossbarClient: crossbar, + gateway: "https://xoracle-1-mn.switchboard.xyz", }) ) ); @@ -962,10 +965,7 @@ export class SolendActionCore { const instructions = [priorityFeeIx, ...responses.map((r) => r[0]!)]; - const tip = this.getTipIx(); - if (tip) { - instructions.push(tip); - } + instructions.push(this.getTipIx()); // Get the latest context const { @@ -990,8 +990,8 @@ export class SolendActionCore { o?.owner.toBase58() === pythSolanaReceiver.receiver.programId.toBase58() ); - if (pythPulledOracles.length) { - const shuffledPriceIds = pythPulledOracles + const shuffledPriceIds = ( + pythPulledOracles .map((pythOracleData, index) => { if (!pythOracleData) { throw new Error(`Could not find oracle data at index ${index}`); @@ -1002,14 +1002,27 @@ export class SolendActionCore { pythOracleData.data ); - return { - key: Math.random(), - priceFeedId: toHexString(priceUpdate.priceMessage.feedId), - }; + const needUpdate = + Date.now() / 1000 - + Number(priceUpdate.priceMessage.publishTime.toString()) > + 70; + + return needUpdate + ? { + key: Math.random(), + priceFeedId: toHexString(priceUpdate.priceMessage.feedId), + } + : undefined; }) - .sort((a, b) => a.key - b.key) - .map((x) => x.priceFeedId); - + .filter(Boolean) as Array<{ + key: number; + priceFeedId: string; + }> + ) + .sort((a, b) => a.key - b.key) + .map((x) => x.priceFeedId); + + if (shuffledPriceIds.length) { const priceFeedUpdateData = await priceServiceConnection.getLatestVaas( shuffledPriceIds ); diff --git a/solend-sdk/src/instructions/instruction.ts b/solend-sdk/src/instructions/instruction.ts index ec033ece..18960140 100644 --- a/solend-sdk/src/instructions/instruction.ts +++ b/solend-sdk/src/instructions/instruction.ts @@ -16,6 +16,8 @@ export enum LendingInstruction { DepositReserveLiquidityAndObligationCollateral = 14, WithdrawObligationCollateralAndRedeemReserveLiquidity = 15, UpdateReserveConfig = 16, + LiquidateObligationAndRedeemReserveCollateral = 17, + RedeemFees = 18, FlashBorrowReserveLiquidity = 19, FlashRepayReserveLiquidity = 20, ForgiveDebt = 21,