diff --git a/README.md b/README.md index 23f420d..c1bb298 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,24 @@ To run liquidator in background: docker-compose up --build -d ``` -To run a specific pool: + +## FAQ +1. How to target a specific market? +The liquidator by default checks the health of all markets (aka isolated pools) e.g main, turbo sol, dog, invictus, etc... If you have the necessary assets in your wallet, the liquidator will attempt to liquidate the unhealhty obligation, otherwise, it simply tells you "insufficient fund" and move on to check the next obligation. If you want to target a specific market, you just need to specify the MARKET param in `docker-compose.yaml` with the market address. You may find all the market address for solend in `https://api.solend.fi/v1/config?deployment=production` + +2. How to change RPC network +By default we use the public solana rpc which is slow and highly rate limited. We strongly suggest using a custom RPC network e.g rpcpool, figment, etc.. so your bot may be more competitive and win some liquidations. Once you have your rpc url, you may specify it in `config.ts` +``` +{ + name: 'production', + endpoint: '', +}, +``` + +3. How to tweak throttling? +If you have a custom rpc network, you would want to disable the default throttling we have set up by specifying the THROTTLE environment variable in `docker-compose.yaml` to 0 ``` -docker-compose up --build liquidator-main -docker-compose up --build liquidator-turbo-sol + - THROTTLE=0 # Throttle not avoid rate limiting ``` ## Support diff --git a/docker-compose.yaml b/docker-compose.yaml index 828752d..567004c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -9,19 +9,8 @@ services: environment: - APP=production - THROTTLE=1900 # Throttle not avoid rate limiting - - MARKET=4UpD2fh7xH3VP9QQaXtsS1YY3bxzWhtfpks7FatyKvdY # Lending market for main pool - secrets: - - keypair # secret to encrypte wallet details in container - - liquidator-turbo-sol: - restart: unless-stopped - build: - context: . - dockerfile: Dockerfile - environment: - - APP=production - - THROTTLE=1900 # Throttle not avoid rate limiting - - MARKET=7RCz8wb6WXxUhAigok9ttgrVgDFFFbibcirECzWSBauM # Lending market for TURBO SOL pool + # Uncomment line below and specify lending market if you only want to target a single pool + # - MARKET=4UpD2fh7xH3VP9QQaXtsS1YY3bxzWhtfpks7FatyKvdY secrets: - keypair # secret to encrypte wallet details in container diff --git a/src/libs/actions/liquidateObligation.ts b/src/libs/actions/liquidateAndRedeem.ts similarity index 72% rename from src/libs/actions/liquidateObligation.ts rename to src/libs/actions/liquidateAndRedeem.ts index e43cb00..f96d70a 100644 --- a/src/libs/actions/liquidateObligation.ts +++ b/src/libs/actions/liquidateAndRedeem.ts @@ -11,15 +11,15 @@ import { import { getTokenInfo } from 'libs/utils'; import { findWhere, map } from 'underscore'; import { refreshReserveInstruction } from 'models/instructions/refreshReserve'; -import { liquidateObligationInstruction } from 'models/instructions/liquidateObligation'; +import { LiquidateObligationAndRedeemReserveCollateral } from 'models/instructions/LiquidateObligationAndRedeemReserveCollateral'; import { refreshObligationInstruction } from 'models/instructions/refreshObligation'; import { Config, Market } from 'global'; -export const liquidateObligation = async ( +export const liquidateAndRedeem = async ( connection: Connection, config: Config, payer: Account, - liquidityAmount: number, + liquidityAmount: number | string, repayTokenSymbol: string, withdrawTokenSymbol: string, lendingMarket: Market, @@ -45,6 +45,7 @@ export const liquidateObligation = async ( ); ixs.push(refreshReserveIx); }); + const refreshObligationIx = refreshObligationInstruction( config, obligation.pubkey, @@ -65,8 +66,8 @@ export const liquidateObligation = async ( const repayReserve = findWhere(lendingMarket.reserves, { asset: repayTokenSymbol }); const withdrawReserve = findWhere(lendingMarket.reserves, { asset: withdrawTokenSymbol }); + const withdrawTokenInfo = getTokenInfo(config, withdrawTokenSymbol); - // get account that will be getting the obligation's token account in return const rewardedWithdrawalCollateralAccount = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, @@ -88,16 +89,41 @@ export const liquidateObligation = async ( ixs.push(createUserCollateralAccountIx); } + const rewardedWithdrawalLiquidityAccount = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + new PublicKey(withdrawTokenInfo.mintAddress), + payer.publicKey, + ); + const rewardedWithdrawalLiquidityAccountInfo = await connection.getAccountInfo( + rewardedWithdrawalLiquidityAccount, + ); + if (!rewardedWithdrawalLiquidityAccountInfo) { + const createUserCollateralAccountIx = Token.createAssociatedTokenAccountInstruction( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + new PublicKey(withdrawTokenInfo.mintAddress), + rewardedWithdrawalLiquidityAccount, + payer.publicKey, + payer.publicKey, + ); + ixs.push(createUserCollateralAccountIx); + } + ixs.push( - liquidateObligationInstruction( + LiquidateObligationAndRedeemReserveCollateral( config, liquidityAmount, repayAccount, rewardedWithdrawalCollateralAccount, + rewardedWithdrawalLiquidityAccount, new PublicKey(repayReserve.address), new PublicKey(repayReserve.liquidityAddress), new PublicKey(withdrawReserve.address), + new PublicKey(withdrawReserve.collateralMintAddress), new PublicKey(withdrawReserve.collateralSupplyAddress), + new PublicKey(withdrawReserve.liquidityAddress), + new PublicKey(withdrawReserve.liquidityFeeReceiverAddress), obligation.pubkey, new PublicKey(lendingMarket.address), new PublicKey(lendingMarket.authorityAddress), @@ -111,8 +137,6 @@ export const liquidateObligation = async ( tx.feePayer = payer.publicKey; tx.sign(payer); - const txHash = await connection.sendRawTransaction(tx.serialize()); - await connection.confirmTransaction(txHash, 'finalized'); - console.log(`liquidated obligation ${obligation.pubkey.toString()} in ${txHash}. - repayToken: ${repayTokenSymbol}. withdrawToken: ${withdrawTokenSymbol}`); + const txHash = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: false }); + await connection.confirmTransaction(txHash, 'processed'); }; diff --git a/src/libs/actions/redeemCollateral.ts b/src/libs/actions/redeemCollateral.ts deleted file mode 100644 index 7e694ee..0000000 --- a/src/libs/actions/redeemCollateral.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - Transaction, - PublicKey, - Connection, - Account, -} from '@solana/web3.js'; -import { - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - Token, -} from '@solana/spl-token'; -import _ from 'underscore'; -import BN from 'bn.js'; -import { redeemReserveCollateralInstruction, refreshReserveInstruction } from 'models/instructions'; -import { getTokenInfo } from 'libs/utils'; -import { Config } from 'global'; - -export async function redeemCollateral( - connection: Connection, - config: Config, - payer: Account, - amountBase: string, - symbol: string, - lendingMarket, -) { - const reserve = _.findWhere(lendingMarket!.reserves, { asset: symbol }); - if (!reserve) { - console.error(`Withdraw: Could not find asset ${symbol} in reserves`); - } - const tokenInfo = getTokenInfo(config, symbol); - const oracleInfo = _.findWhere(config.oracles.assets, { asset: symbol }); - if (!oracleInfo) { - console.error(`Withdraw: Could not find oracle for ${symbol}`); - } - - const ixs = [] as any; - - // refreshed reserve is required - const refreshReserveIx = refreshReserveInstruction( - config, - new PublicKey(reserve.address), - new PublicKey(oracleInfo!.priceAddress), - new PublicKey(oracleInfo!.switchboardFeedAddress), - ); - ixs.push(refreshReserveIx); - - // Get collateral account address - const userCollateralAccountAddress = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - new PublicKey(reserve.collateralMintAddress), - payer.publicKey, - ); - - // Get or create user token account - const userTokenAccountAddress = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - new PublicKey(tokenInfo!.mintAddress), - payer.publicKey, - ); - const userTokenAccountInfo = await connection.getAccountInfo( - userTokenAccountAddress, - ); - - if (!userTokenAccountInfo) { - const createUserTokenAccountIx = Token.createAssociatedTokenAccountInstruction( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - new PublicKey(tokenInfo!.mintAddress), - userTokenAccountAddress, - payer.publicKey, - payer.publicKey, - ); - ixs.push(createUserTokenAccountIx); - } - const withdrawObligationCollateralAndRedeemReserveLiquidityIx = redeemReserveCollateralInstruction( - config, - new BN(amountBase), - userCollateralAccountAddress, // source collateral account - userTokenAccountAddress, // destinationLiquidity - new PublicKey(reserve.address), - new PublicKey(reserve.collateralMintAddress), - new PublicKey(reserve.liquidityAddress), - new PublicKey(lendingMarket.address), - new PublicKey(lendingMarket.authorityAddress), - payer.publicKey, // transferAuthority - ); - ixs.push(withdrawObligationCollateralAndRedeemReserveLiquidityIx); - - const tx = new Transaction().add(...ixs); - const { blockhash } = await connection.getRecentBlockhash(); - tx.recentBlockhash = blockhash; - tx.feePayer = payer.publicKey; - tx.sign(payer); - - try { - const txHash = await connection.sendRawTransaction(tx.serialize()); - await connection.confirmTransaction(txHash); - console.log(`successfully redeemed ${symbol} collaterals`); - } catch (err) { - console.error('error redeeming collateral: ', err); - } -} diff --git a/src/libs/utils.ts b/src/libs/utils.ts index a46ded2..3456841 100644 --- a/src/libs/utils.ts +++ b/src/libs/utils.ts @@ -104,14 +104,14 @@ function stripEnd(s: string, c: string) { return s.slice(0, i + 1); } -export async function getObligations(connection: Connection, config: Config, lendingMarketPubKey) { +export async function getObligations(connection: Connection, config: Config, lendingMarket) { const resp = await connection.getProgramAccounts(new PublicKey(config.programID), { commitment: connection.commitment, filters: [ { memcmp: { offset: 10, - bytes: lendingMarketPubKey.toBase58(), + bytes: lendingMarket, }, }, { @@ -123,14 +123,14 @@ export async function getObligations(connection: Connection, config: Config, len return resp.map((account) => ObligationParser(account.pubkey, account.account)); } -export async function getReserves(connection: Connection, config: Config, lendingMarketPubKey) { +export async function getReserves(connection: Connection, config: Config, lendingMarket) { const resp = await connection.getProgramAccounts(new PublicKey(config.programID), { commitment: connection.commitment, filters: [ { memcmp: { offset: 10, - bytes: lendingMarketPubKey.toBase58(), + bytes: lendingMarket, }, }, { diff --git a/src/liquidate.ts b/src/liquidate.ts index fccd450..0bbfa9a 100644 --- a/src/liquidate.ts +++ b/src/liquidate.ts @@ -1,144 +1,131 @@ /* eslint-disable no-continue */ -/* eslint-disable no-constant-condition */ /* eslint-disable no-restricted-syntax */ import { Account, Connection, PublicKey, } from '@solana/web3.js'; -import _ from 'underscore'; import dotenv from 'dotenv'; -import { liquidateObligation } from 'libs/actions/liquidateObligation'; import { ObligationParser } from 'models/layouts/obligation'; import { - getCollateralBalances, getObligations, getReserves, getWalletTokenData, wait, } from 'libs/utils'; import { getTokensOracleData } from 'libs/pyth'; import { calculateRefreshedObligation } from 'libs/refreshObligation'; -import { redeemCollateral } from 'libs/actions/redeemCollateral'; import { readSecret } from 'libs/secret'; +import { liquidateAndRedeem } from 'libs/actions/liquidateAndRedeem'; import { clusterUrl, getConfig } from './config'; dotenv.config(); async function runLiquidator() { - const marketAddress = process.env.MARKET; - if (!marketAddress) { - throw new Error('no process.env.MARKET provided'); - } const config = await getConfig(); - const lendingMarkets = _.findWhere(config.markets, { address: marketAddress }); - const { reserves } = lendingMarkets; const connection = new Connection(clusterUrl!.endpoint, 'confirmed'); - const lendingMarketPubKey = new PublicKey(lendingMarkets.address); // liquidator's keypair. const payer = new Account(JSON.parse(readSecret('keypair'))); console.log(` app: ${process.env.APP} - lendingMarket: ${marketAddress} clusterUrl: ${clusterUrl!.endpoint} wallet: ${payer.publicKey.toBase58()} `); for (let epoch = 0; ; epoch += 1) { - const tokensOracle = await getTokensOracleData(connection, config, reserves); - const allObligations = await getObligations(connection, config, lendingMarketPubKey); - const allReserves = await getReserves(connection, config, lendingMarketPubKey); - - for (let obligation of allObligations) { - try { - while (obligation) { - const { - borrowedValue, - unhealthyBorrowValue, - deposits, - borrows, - } = calculateRefreshedObligation( - obligation.info, - allReserves, - tokensOracle, - ); - - // Do nothing if obligation is healthy - if (borrowedValue.isLessThanOrEqualTo(unhealthyBorrowValue)) { - break; - } + for (const market of config.markets) { + // Target specific market if MARKET is specified in docker-compose.yaml + if (process.env.MARKET && process.env.MARKET !== market.address) { + continue; + } - console.log( - `Obligation ${obligation.pubkey.toString()} is underwater`, - 'borrowedValue: ', borrowedValue.toString(), - 'unhealthyBorrowValue', unhealthyBorrowValue.toString(), - ); - - // select repay token that has the highest market value - let selectedBorrow; - borrows.forEach((borrow) => { - if (!selectedBorrow || borrow.marketValue.gt(selectedBorrow.marketValue)) { - selectedBorrow = borrow; + const tokensOracle = await getTokensOracleData(connection, config, market.reserves); + const allObligations = await getObligations(connection, config, market.address); + const allReserves = await getReserves(connection, config, market.address); + + for (let obligation of allObligations) { + try { + while (obligation) { + const { + borrowedValue, + unhealthyBorrowValue, + deposits, + borrows, + } = calculateRefreshedObligation( + obligation.info, + allReserves, + tokensOracle, + ); + + // Do nothing if obligation is healthy + if (borrowedValue.isLessThanOrEqualTo(unhealthyBorrowValue)) { + break; } - }); - // select the withdrawal collateral token with the highest market value - let selectedDeposit; - deposits.forEach((deposit) => { - if (!selectedDeposit || deposit.marketValue.gt(selectedDeposit.marketValue)) { - selectedDeposit = deposit; + // select repay token that has the highest market value + let selectedBorrow; + borrows.forEach((borrow) => { + if (!selectedBorrow || borrow.marketValue.gt(selectedBorrow.marketValue)) { + selectedBorrow = borrow; + } + }); + + // select the withdrawal collateral token with the highest market value + let selectedDeposit; + deposits.forEach((deposit) => { + if (!selectedDeposit || deposit.marketValue.gt(selectedDeposit.marketValue)) { + selectedDeposit = deposit; + } + }); + + if (!selectedBorrow || !selectedDeposit) { + // skip toxic obligations caused by toxic oracle data + break; } - }); - if (!selectedBorrow || !selectedDeposit) { - // skip toxic obligations caused by toxic oracle data - break; - } + console.log(`Obligation ${obligation.pubkey.toString()} is underwater + borrowedValue: ${borrowedValue.toString()} + unhealthyBorrowValue: ${unhealthyBorrowValue.toString()} + market address: ${market.address}`); + + // get wallet balance for selected borrow token + const { balanceBase } = await getWalletTokenData(connection, config, payer, selectedBorrow.mintAddress, selectedBorrow.symbol); + if (balanceBase === 0) { + console.log(`insufficient ${selectedBorrow.symbol} to liquidate obligation ${obligation.pubkey.toString()} in market: ${market.address}`); + break; + } else if (balanceBase < 0) { + console.log(`failed to get wallet balance for ${selectedBorrow.symbol} to liquidate obligation ${obligation.pubkey.toString()} in market: ${market.address}. + Potentially network error or token account does not exist in wallet`); + break; + } - // get wallet balance for selected borrow token - const { balanceBase } = await getWalletTokenData(connection, config, payer, selectedBorrow.mintAddress, selectedBorrow.symbol); - if (balanceBase === 0) { - console.log(`insufficient ${selectedBorrow.symbol} to liquidate obligation ${obligation.pubkey.toString()}`); - break; - } else if (balanceBase < 0) { - console.log(`failed to get wallet balance for ${selectedBorrow.symbol}. Potentially network error or token account does not exist in wallet`); - break; + // Set super high liquidation amount which acts as u64::MAX as program will only liquidate max + // 50% val of all borrowed assets. + await liquidateAndRedeem( + connection, + config, + payer, + balanceBase, + selectedBorrow.symbol, + selectedDeposit.symbol, + market, + obligation, + ); + + const postLiquidationObligation = await connection.getAccountInfo( + new PublicKey(obligation.pubkey), + ); + obligation = ObligationParser(obligation.pubkey, postLiquidationObligation!); } - - // Set super high liquidation amount which acts as u64::MAX as program will only liquidate max - // 50% val of all borrowed assets. - await liquidateObligation( - connection, - config, - payer, - balanceBase, - selectedBorrow.symbol, - selectedDeposit.symbol, - lendingMarkets, - obligation, - ); - - const postLiquidationObligation = await connection.getAccountInfo( - new PublicKey(obligation.pubkey), - ); - obligation = ObligationParser(obligation.pubkey, postLiquidationObligation!); + } catch (err) { + console.error(`error liquidating ${obligation!.pubkey.toString()}: `, err); + continue; } - } catch (err) { - console.error(`error liquidating ${obligation!.pubkey.toString()}: `, err); - continue; } - } - // check if collateral redeeming is required - const collateralBalances = await getCollateralBalances(connection, config, payer, reserves); - collateralBalances.forEach(({ balanceBase, symbol }) => { - if (balanceBase > 0) { - redeemCollateral(connection, config, payer, balanceBase.toString(), symbol, lendingMarkets); + // Throttle to avoid rate limiter + if (process.env.THROTTLE) { + await wait(process.env.THROTTLE); } - }); - - // Throttle to avoid rate limiter - if (process.env.THROTTLE) { - await wait(process.env.THROTTLE); } } } diff --git a/src/models/instructions/liquidateObligation.ts b/src/models/instructions/LiquidateObligationAndRedeemReserveCollateral.ts similarity index 61% rename from src/models/instructions/liquidateObligation.ts rename to src/models/instructions/LiquidateObligationAndRedeemReserveCollateral.ts index 860e67e..4bed0c1 100644 --- a/src/models/instructions/liquidateObligation.ts +++ b/src/models/instructions/LiquidateObligationAndRedeemReserveCollateral.ts @@ -1,6 +1,6 @@ import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { - PublicKey, SYSVAR_CLOCK_PUBKEY, TransactionInstruction, + PublicKey, TransactionInstruction, } from '@solana/web3.js'; import BN from 'bn.js'; import * as BufferLayout from 'buffer-layout'; @@ -16,25 +16,32 @@ import { LendingInstruction } from './instruction'; /// $authority can transfer $liquidity_amount. /// 1. `[writable]` Destination collateral token account. /// Minted by withdraw reserve collateral mint. -/// 2. `[writable]` Repay reserve account - refreshed. -/// 3. `[writable]` Repay reserve liquidity supply SPL Token account. -/// 4. `[]` Withdraw reserve account - refreshed. -/// 5. `[writable]` Withdraw reserve collateral supply SPL Token account. -/// 6. `[writable]` Obligation account - refreshed. -/// 7. `[]` Lending market account. -/// 8. `[]` Derived lending market authority. -/// 9. `[signer]` User transfer authority ($authority). -/// 10 `[]` Clock sysvar. -/// 11 `[]` Token program id. -export const liquidateObligationInstruction = ( +/// 2. `[writable]` Destination liquidity token account. +/// 3. `[writable]` Repay reserve account - refreshed. +/// 4. `[writable]` Repay reserve liquidity supply SPL Token account. +/// 5. `[writable]` Withdraw reserve account - refreshed. +/// 6. `[writable]` Withdraw reserve collateral SPL Token mint. +/// 7. `[writable]` Withdraw reserve collateral supply SPL Token account. +/// 8. `[writable]` Withdraw reserve liquidity supply SPL Token account. +/// 9. `[writable]` Withdraw reserve liquidity fee receiver account. +/// 10 `[writable]` Obligation account - refreshed. +/// 11 `[]` Lending market account. +/// 12 `[]` Derived lending market authority. +/// 13 `[signer]` User transfer authority ($authority). +/// 14 `[]` Token program id. +export const LiquidateObligationAndRedeemReserveCollateral = ( config: Config, liquidityAmount: number | BN | string, sourceLiquidity: PublicKey, destinationCollateral: PublicKey, + destinationRewardLiquidity: PublicKey, repayReserve: PublicKey, repayReserveLiquiditySupply: PublicKey, withdrawReserve: PublicKey, + withdrawReserveCollateralMint: PublicKey, withdrawReserveCollateralSupply: PublicKey, + withdrawReserveLiquiditySupply: PublicKey, + withdrawReserveFeeReceiver: PublicKey, obligation: PublicKey, lendingMarket: PublicKey, lendingMarketAuthority: PublicKey, @@ -48,31 +55,31 @@ export const liquidateObligationInstruction = ( const data = Buffer.alloc(dataLayout.span); dataLayout.encode( { - instruction: LendingInstruction.LiquidateObligation, + instruction: LendingInstruction.LiquidateObligationAndRedeemReserveCollateral, liquidityAmount: new BN(liquidityAmount), }, data, ); - console.log('repay reserve', repayReserve.toString()); - console.log('withdraw reserve', withdrawReserve.toString()); - const keys = [ { pubkey: sourceLiquidity, isSigner: false, isWritable: true }, { pubkey: destinationCollateral, isSigner: false, isWritable: true }, + { pubkey: destinationRewardLiquidity, isSigner: false, isWritable: true }, { pubkey: repayReserve, isSigner: false, isWritable: true }, { pubkey: repayReserveLiquiditySupply, isSigner: false, isWritable: true }, - { pubkey: withdrawReserve, isSigner: false, isWritable: false }, + { pubkey: withdrawReserve, isSigner: false, isWritable: true }, + { pubkey: withdrawReserveCollateralMint, isSigner: false, isWritable: true }, { pubkey: withdrawReserveCollateralSupply, isSigner: false, isWritable: true, }, + { pubkey: withdrawReserveLiquiditySupply, isSigner: false, isWritable: true }, + { pubkey: withdrawReserveFeeReceiver, isSigner: false, isWritable: true }, { pubkey: obligation, isSigner: false, isWritable: true }, { pubkey: lendingMarket, isSigner: false, isWritable: false }, { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, ]; diff --git a/src/models/instructions/index.ts b/src/models/instructions/index.ts index 79fc4f3..55e9696 100644 --- a/src/models/instructions/index.ts +++ b/src/models/instructions/index.ts @@ -1,4 +1,3 @@ export * from './refreshObligation'; export * from './refreshReserve'; -export * from './redeemReserveCollateral'; -export * from './liquidateObligation'; +export * from './LiquidateObligationAndRedeemReserveCollateral'; diff --git a/src/models/instructions/instruction.ts b/src/models/instructions/instruction.ts index a08ae5a..9da354b 100644 --- a/src/models/instructions/instruction.ts +++ b/src/models/instructions/instruction.ts @@ -15,4 +15,6 @@ export enum LendingInstruction { FlashLoan = 13, DepositReserveLiquidityAndObligationCollateral = 14, WithdrawObligationCollateralAndRedeemReserveLiquidity = 15, + UpdateReserveConfig = 16, + LiquidateObligationAndRedeemReserveCollateral = 17, } diff --git a/src/models/instructions/redeemReserveCollateral.ts b/src/models/instructions/redeemReserveCollateral.ts deleted file mode 100644 index 0ad287d..0000000 --- a/src/models/instructions/redeemReserveCollateral.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - TransactionInstruction, -} from '@solana/web3.js'; -import BN from 'bn.js'; -import * as BufferLayout from 'buffer-layout'; -import { Config } from 'global'; -import * as Layout from 'libs/layout'; -import { LendingInstruction } from './instruction'; - -/// Redeem collateral from a reserve in exchange for liquidity. -/// -/// Accounts expected by this instruction: -/// -/// 0. `[writable]` Source collateral token account. -/// $authority can transfer $collateral_amount. -/// 1. `[writable]` Destination liquidity token account. -/// 2. `[writable]` Reserve account. -/// 3. `[writable]` Reserve collateral SPL Token mint. -/// 4. `[writable]` Reserve liquidity supply SPL Token account. -/// 5. `[]` Lending market account. -/// 6. `[]` Derived lending market authority. -/// 7. `[signer]` User transfer authority ($authority). -/// 8. `[]` Clock sysvar. -/// 9. `[]` Token program id. -export const redeemReserveCollateralInstruction = ( - config: Config, - collateralAmount: number | BN, - sourceCollateral: PublicKey, - destinationLiquidity: PublicKey, - reserve: PublicKey, - reserveCollateralMint: PublicKey, - reserveLiquiditySupply: PublicKey, - lendingMarket: PublicKey, - lendingMarketAuthority: PublicKey, - transferAuthority: PublicKey, -): TransactionInstruction => { - const dataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('collateralAmount'), - ]); - - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: LendingInstruction.RedeemReserveCollateral, - collateralAmount: new BN(collateralAmount), - }, - data, - ); - - const keys = [ - { pubkey: sourceCollateral, isSigner: false, isWritable: true }, - { pubkey: destinationLiquidity, isSigner: false, isWritable: true }, - { pubkey: reserve, isSigner: false, isWritable: true }, - { pubkey: reserveCollateralMint, isSigner: false, isWritable: true }, - { pubkey: reserveLiquiditySupply, isSigner: false, isWritable: true }, - { pubkey: lendingMarket, isSigner: false, isWritable: false }, - { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, - { pubkey: transferAuthority, isSigner: true, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - return new TransactionInstruction({ - keys, - programId: new PublicKey(config.programID), - data, - }); -};