This commit is contained in:
jordansexton 2021-04-22 18:51:41 -05:00
parent c4be0d78e5
commit b9a71a423a
11 changed files with 496 additions and 607 deletions

View File

@ -1,293 +0,0 @@
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
contexts,
utils,
actions,
models,
TokenAccount,
ParsedAccount,
} from '@oyster/common';
import {
accrueInterestInstruction,
LendingReserve,
} from './../models/lending/reserve';
import { AccountLayout, MintInfo, MintLayout } from '@solana/spl-token';
import { createUninitializedObligation } from './obligation';
import {
LendingObligationLayout,
borrowInstruction,
LendingMarket,
BorrowAmountType,
LendingObligation,
initObligationInstruction,
} from '../models';
const { approve } = models;
const { toLamports, LENDING_PROGRAM_ID, LEND_HOST_FEE_ADDRESS, notify } = utils;
const { cache, MintParser } = contexts.Accounts;
const { sendTransaction } = contexts.Connection;
const {
createTempMemoryAccount,
createUninitializedAccount,
createUninitializedMint,
ensureSplAccount,
findOrCreateAccountByMint,
} = actions;
export const borrow = async (
connection: Connection,
wallet: any,
from: TokenAccount,
amount: number,
amountType: BorrowAmountType,
borrowReserve: ParsedAccount<LendingReserve>,
depositReserve: ParsedAccount<LendingReserve>,
existingObligation?: ParsedAccount<LendingObligation>,
obligationAccount?: PublicKey,
) => {
notify({
message: 'Borrowing funds...',
description: 'Please review transactions to approve.',
type: 'warn',
});
let signers: Account[] = [];
let instructions: TransactionInstruction[] = [];
let cleanupInstructions: TransactionInstruction[] = [];
let finalCleanupInstructions: TransactionInstruction[] = [];
const [authority] = await PublicKey.findProgramAddress(
[depositReserve.info.lendingMarket.toBuffer()],
LENDING_PROGRAM_ID,
);
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
const obligation = existingObligation
? existingObligation.pubkey
: createUninitializedObligation(
instructions,
wallet.publicKey,
await connection.getMinimumBalanceForRentExemption(
LendingObligationLayout.span,
),
signers,
);
const obligationMint = existingObligation
? existingObligation.info.tokenMint
: createUninitializedMint(
instructions,
wallet.publicKey,
await connection.getMinimumBalanceForRentExemption(MintLayout.span),
signers,
);
const obligationTokenOutput = obligationAccount
? obligationAccount
: createUninitializedAccount(
instructions,
wallet.publicKey,
accountRentExempt,
signers,
);
if (!obligationAccount) {
instructions.push(
initObligationInstruction(
depositReserve.pubkey,
borrowReserve.pubkey,
obligation,
obligationMint,
obligationTokenOutput,
wallet.publicKey,
depositReserve.info.lendingMarket,
authority,
),
);
}
// Creates host fee account if it doesn't exsist
let hostFeeReceiver = LEND_HOST_FEE_ADDRESS
? findOrCreateAccountByMint(
wallet.publicKey,
LEND_HOST_FEE_ADDRESS,
instructions,
[],
accountRentExempt,
depositReserve.info.collateralMint,
signers,
)
: undefined;
let amountLamports: number = 0;
let fromLamports: number = 0;
if (amountType === BorrowAmountType.LiquidityBorrowAmount) {
// approve max transfer
// TODO: improve contrain by using dex market data
const approvedAmount = from.info.amount.toNumber();
fromLamports = approvedAmount - accountRentExempt;
const mint = (await cache.query(
connection,
borrowReserve.info.liquidityMint,
MintParser,
)) as ParsedAccount<MintInfo>;
amountLamports = toLamports(amount, mint?.info);
} else if (amountType === BorrowAmountType.CollateralDepositAmount) {
const mint = (await cache.query(
connection,
depositReserve.info.collateralMint,
MintParser,
)) as ParsedAccount<MintInfo>;
amountLamports = toLamports(amount, mint?.info);
fromLamports = amountLamports;
}
const fromAccount = ensureSplAccount(
instructions,
finalCleanupInstructions,
from,
wallet.publicKey,
fromLamports + accountRentExempt,
signers,
);
let toAccount = await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
finalCleanupInstructions,
accountRentExempt,
borrowReserve.info.liquidityMint,
signers,
);
if (instructions.length > 0) {
// create all accounts in one transaction
let { txid } = await sendTransaction(connection, wallet, instructions, [
...signers,
]);
notify({
message: 'Obligation accounts created',
description: `Transaction ${txid}`,
type: 'success',
});
}
notify({
message: 'Borrowing funds...',
description: 'Please review transactions to approve.',
type: 'warn',
});
signers = [];
instructions = [];
cleanupInstructions = [...finalCleanupInstructions];
// create approval for transfer transactions
const transferAuthority = approve(
instructions,
cleanupInstructions,
fromAccount,
wallet.publicKey,
fromLamports,
false,
);
signers.push(transferAuthority);
const dexMarketAddress = borrowReserve.info.dexMarketOption
? borrowReserve.info.dexMarket
: depositReserve.info.dexMarket;
const dexMarket = cache.get(dexMarketAddress);
if (!dexMarket) {
throw new Error(`Dex market doesn't exist.`);
}
const market = cache.get(
depositReserve.info.lendingMarket,
) as ParsedAccount<LendingMarket>;
const dexOrderBookSide = market.info.quoteMint.equals(
depositReserve.info.liquidityMint,
)
? dexMarket?.info.asks
: dexMarket?.info.bids;
const memory = createTempMemoryAccount(
instructions,
wallet.publicKey,
signers,
LENDING_PROGRAM_ID,
);
instructions.push(
accrueInterestInstruction(depositReserve.pubkey, borrowReserve.pubkey),
);
// borrow
instructions.push(
borrowInstruction(
amountLamports,
amountType,
fromAccount,
toAccount,
depositReserve.pubkey,
depositReserve.info.collateralSupply,
depositReserve.info.collateralFeesReceiver,
borrowReserve.pubkey,
borrowReserve.info.liquiditySupply,
obligation,
obligationMint,
obligationTokenOutput,
depositReserve.info.lendingMarket,
authority,
transferAuthority.publicKey,
dexMarketAddress,
dexOrderBookSide,
memory,
hostFeeReceiver,
),
);
try {
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers,
true,
);
notify({
message: 'Funds borrowed.',
type: 'success',
description: `Transaction - ${txid}`,
});
} catch (ex) {
console.error(ex);
throw new Error();
}
};

View File

@ -0,0 +1,157 @@
import {
contexts,
findOrCreateAccountByMint,
LEND_HOST_FEE_ADDRESS,
LENDING_PROGRAM_ID,
notify,
ParsedAccount,
toLamports,
} from '@oyster/common';
import { AccountLayout, MintInfo } from '@solana/spl-token';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
borrowObligationLiquidityInstruction,
Obligation,
refreshObligationInstruction,
refreshReserveInstruction,
Reserve,
} from '../models';
const { cache, MintParser } = contexts.Accounts;
const { sendTransaction } = contexts.Connection;
// @FIXME
export const borrowObligationLiquidity = async (
connection: Connection,
wallet: any,
liquidityAmount: number,
borrowReserve: ParsedAccount<Reserve>,
obligation: ParsedAccount<Obligation>,
) => {
notify({
message: 'Borrowing funds...',
description: 'Please review transactions to approve.',
type: 'warn',
});
let signers: Account[] = [];
let instructions: TransactionInstruction[] = [];
let cleanupInstructions: TransactionInstruction[] = [];
let finalCleanupInstructions: TransactionInstruction[] = [];
const [lendingMarketAuthority] = await PublicKey.findProgramAddress(
[borrowReserve.info.lendingMarket.toBuffer()],
LENDING_PROGRAM_ID,
);
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
// Creates host fee account if it doesn't exist
let hostFeeReceiver = LEND_HOST_FEE_ADDRESS
? findOrCreateAccountByMint(
wallet.publicKey,
LEND_HOST_FEE_ADDRESS,
instructions,
[],
accountRentExempt,
borrowReserve.info.liquidity.mint,
signers,
)
: undefined;
const mint = (await cache.query(
connection,
borrowReserve.info.liquidity.mint,
MintParser,
)) as ParsedAccount<MintInfo>;
// @TODO: handle 100% -> u64::MAX
const amountLamports = toLamports(liquidityAmount, mint?.info);
let destinationLiquidity = await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
finalCleanupInstructions,
accountRentExempt,
borrowReserve.info.liquidity.mint,
signers,
);
if (instructions.length > 0) {
// create all accounts in one transaction
let { txid } = await sendTransaction(connection, wallet, instructions, [
...signers,
]);
notify({
// @TODO: change message
message: 'Obligation accounts created',
description: `Transaction ${txid}`,
type: 'success',
});
}
notify({
message: 'Borrowing funds...',
description: 'Please review transactions to approve.',
type: 'warn',
});
// @FIXME: signers
signers = [];
instructions = [];
cleanupInstructions = [...finalCleanupInstructions];
instructions.push(
refreshReserveInstruction(
borrowReserve.pubkey,
borrowReserve.info.liquidity.aggregatorOption
? borrowReserve.info.liquidity.aggregator
: undefined,
),
refreshObligationInstruction(
obligation.pubkey,
obligation.info.deposits.map((collateral) => collateral.depositReserve),
obligation.info.borrows.map((liquidity) => liquidity.borrowReserve),
),
borrowObligationLiquidityInstruction(
amountLamports,
borrowReserve.info.liquidity.supply,
destinationLiquidity,
borrowReserve.pubkey,
borrowReserve.info.liquidity.feeReceiver,
obligation.pubkey,
borrowReserve.info.lendingMarket,
lendingMarketAuthority,
obligation.info.owner,
hostFeeReceiver,
),
);
try {
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers,
true,
);
notify({
message: 'Funds borrowed.',
type: 'success',
description: `Transaction - ${txid}`,
});
} catch (ex) {
console.error(ex);
throw new Error();
}
};

View File

@ -1,25 +1,26 @@
import { LENDING_PROGRAM_ID } from '@oyster/common';
import {
Account,
PublicKey,
SystemProgram,
TransactionInstruction,
} from '@solana/web3.js';
import { utils } from '@oyster/common';
import { LendingObligationLayout } from '../models';
const { LENDING_PROGRAM_ID } = utils;
export function createUninitializedObligation(
export function createAccount(
instructions: TransactionInstruction[],
payer: PublicKey,
amount: number,
signers: Account[],
space: number,
) {
const account = new Account();
instructions.push(
SystemProgram.createAccount({
fromPubkey: payer,
newAccountPubkey: account.publicKey,
lamports: amount,
space: LendingObligationLayout.span,
space,
programId: LENDING_PROGRAM_ID,
}),
);

View File

@ -0,0 +1,18 @@
import { Account, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { ObligationLayout } from '../models';
import { createAccount } from './createAccount';
export function createObligation(
instructions: TransactionInstruction[],
payer: PublicKey,
amount: number,
signers: Account[],
) {
return createAccount(
instructions,
payer,
amount,
signers,
ObligationLayout.span,
);
}

View File

@ -0,0 +1,18 @@
import { Account, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { ReserveLayout } from '../models';
import { createAccount } from './createAccount';
export function createReserve(
instructions: TransactionInstruction[],
payer: PublicKey,
amount: number,
signers: Account[],
) {
return createAccount(
instructions,
payer,
amount,
signers,
ReserveLayout.span,
);
}

View File

@ -1,20 +1,26 @@
import {
actions,
contexts,
LENDING_PROGRAM_ID,
models,
notify,
TokenAccount,
} from '@oyster/common';
import { AccountLayout } from '@solana/spl-token';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import { contexts, utils, models, actions, TokenAccount } from '@oyster/common';
import {
accrueInterestInstruction,
depositInstruction,
depositReserveLiquidityInstruction,
initReserveInstruction,
LendingReserve,
} from './../models/lending';
import { AccountLayout } from '@solana/spl-token';
refreshReserveInstruction,
Reserve,
} from '../models';
const { sendTransaction } = contexts.Connection;
const { LENDING_PROGRAM_ID, notify } = utils;
const {
createUninitializedAccount,
ensureSplAccount,
@ -22,13 +28,14 @@ const {
} = actions;
const { approve } = models;
export const deposit = async (
from: TokenAccount,
amountLamports: number,
reserve: LendingReserve,
reserveAddress: PublicKey,
// @FIXME: split up into deposit, and init which requires lending market owner
export const depositReserveLiquidity = async (
connection: Connection,
wallet: any,
liquidityAmount: number,
source: TokenAccount,
reserve: Reserve,
reserveAddress: PublicKey,
) => {
notify({
message: 'Depositing funds...',
@ -47,17 +54,17 @@ export const deposit = async (
AccountLayout.span,
);
const [authority] = await PublicKey.findProgramAddress(
const [lendingMarketAuthority] = await PublicKey.findProgramAddress(
[reserve.lendingMarket.toBuffer()], // which account should be authority
LENDING_PROGRAM_ID,
);
const fromAccount = ensureSplAccount(
const sourceLiquidityAccount = ensureSplAccount(
instructions,
cleanupInstructions,
from,
source,
wallet.publicKey,
amountLamports + accountRentExempt,
liquidityAmount + accountRentExempt,
signers,
);
@ -65,75 +72,80 @@ export const deposit = async (
const transferAuthority = approve(
instructions,
cleanupInstructions,
fromAccount,
sourceLiquidityAccount,
wallet.publicKey,
amountLamports,
liquidityAmount,
);
signers.push(transferAuthority);
let toAccount: PublicKey;
if (isInitalized) {
// get destination account
toAccount = await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
reserve.collateralMint,
signers,
);
} else {
toAccount = createUninitializedAccount(
instructions,
wallet.publicKey,
accountRentExempt,
signers,
);
}
let destinationCollateralAccount: PublicKey = isInitalized
? await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
reserve.collateral.mint,
signers,
)
: createUninitializedAccount(
instructions,
wallet.publicKey,
accountRentExempt,
signers,
);
if (isInitalized) {
instructions.push(accrueInterestInstruction(reserveAddress));
// deposit
instructions.push(
depositInstruction(
amountLamports,
fromAccount,
toAccount,
reserve.lendingMarket,
authority,
transferAuthority.publicKey,
refreshReserveInstruction(
reserveAddress,
reserve.liquiditySupply,
reserve.collateralMint,
reserve.liquidity.aggregatorOption
? reserve.liquidity.aggregator
: undefined,
),
depositReserveLiquidityInstruction(
liquidityAmount,
sourceLiquidityAccount,
destinationCollateralAccount,
reserveAddress,
reserve.liquidity.supply,
reserve.collateral.mint,
reserve.lendingMarket,
lendingMarketAuthority,
transferAuthority.publicKey,
),
);
} else {
// TODO: finish reserve init
// @FIXME: reserve config
const MAX_UTILIZATION_RATE = 80;
instructions.push(
initReserveInstruction(
amountLamports,
liquidityAmount,
MAX_UTILIZATION_RATE,
fromAccount,
toAccount,
sourceLiquidityAccount,
destinationCollateralAccount,
reserveAddress,
reserve.liquidityMint,
reserve.liquiditySupply,
reserve.collateralMint,
reserve.collateralSupply,
reserve.liquidity.mint,
reserve.liquidity.supply,
reserve.liquidity.feeReceiver,
reserve.collateral.mint,
reserve.collateral.supply,
reserve.lendingMarket,
authority,
lendingMarketAuthority,
// @FIXME: lending market owner
lendingMarketOwner,
transferAuthority.publicKey,
reserve.dexMarket,
reserve.liquidity.aggregatorOption
? reserve.liquidity.aggregator
: undefined,
),
);
}
try {
let { txid } = await sendTransaction(
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),

View File

@ -1,5 +1,7 @@
export { borrow } from './borrow';
export { deposit } from './deposit';
export { repay } from './repay';
export { withdraw } from './withdraw';
export { liquidate } from './liquidate';
export { borrowObligationLiquidity } from './borrowObligationLiquidity';
export { depositReserveLiquidity } from './depositReserveLiquidity';
export { repayObligationLiquidity } from './repayObligationLiquidity';
export { redeemReserveCollateral } from './redeemReserveCollateral';
export { liquidateObligation } from './liquidateObligation';
// @TODO: add actions for other instructions

View File

@ -1,3 +1,15 @@
import {
contexts,
createTempMemoryAccount,
ensureSplAccount,
findOrCreateAccountByMint,
LENDING_PROGRAM_ID,
models,
notify,
ParsedAccount,
TokenAccount,
} from '@oyster/common';
import { AccountLayout } from '@solana/spl-token';
import {
Account,
Connection,
@ -5,42 +17,26 @@ import {
TransactionInstruction,
} from '@solana/web3.js';
import {
contexts,
utils,
actions,
models,
ParsedAccount,
TokenAccount,
} from '@oyster/common';
import {
accrueInterestInstruction,
LendingReserve,
} from './../models/lending/reserve';
import { liquidateInstruction } from './../models/lending/liquidate';
import { AccountLayout } from '@solana/spl-token';
import { LendingMarket, LendingObligation } from '../models';
LendingMarket,
liquidateObligationInstruction,
Obligation,
refreshReserveInstruction,
Reserve,
} from '../models';
const { cache } = contexts.Accounts;
const { approve } = models;
const {
createTempMemoryAccount,
ensureSplAccount,
findOrCreateAccountByMint,
} = actions;
const { sendTransaction } = contexts.Connection;
const { LENDING_PROGRAM_ID, notify } = utils;
export const liquidate = async (
// @FIXME
export const liquidateObligation = async (
connection: Connection,
wallet: any,
from: TokenAccount, // liquidity account
amountLamports: number, // in liquidty token (lamports)
// which loan to repay
obligation: ParsedAccount<LendingObligation>,
repayReserve: ParsedAccount<LendingReserve>,
withdrawReserve: ParsedAccount<LendingReserve>,
liquidityAmount: number,
source: TokenAccount,
repayReserve: ParsedAccount<Reserve>,
withdrawReserve: ParsedAccount<Reserve>,
obligation: ParsedAccount<Obligation>,
) => {
notify({
message: 'Repaying funds...',
@ -57,17 +53,17 @@ export const liquidate = async (
AccountLayout.span,
);
const [authority] = await PublicKey.findProgramAddress(
const [lendingMarketAuthority] = await PublicKey.findProgramAddress(
[repayReserve.info.lendingMarket.toBuffer()],
LENDING_PROGRAM_ID,
);
const fromAccount = ensureSplAccount(
const sourceAccount = ensureSplAccount(
instructions,
cleanupInstructions,
from,
source,
wallet.publicKey,
amountLamports + accountRentExempt,
liquidityAmount + accountRentExempt,
signers,
);
@ -75,9 +71,9 @@ export const liquidate = async (
const transferAuthority = approve(
instructions,
cleanupInstructions,
fromAccount,
sourceAccount,
wallet.publicKey,
amountLamports,
liquidityAmount,
);
signers.push(transferAuthority);
@ -88,16 +84,17 @@ export const liquidate = async (
instructions,
cleanupInstructions,
accountRentExempt,
withdrawReserve.info.collateralMint,
withdrawReserve.info.collateral.mint,
signers,
);
const dexMarketAddress = repayReserve.info.dexMarketOption
? repayReserve.info.dexMarket
: withdrawReserve.info.dexMarket;
const dexMarket = cache.get(dexMarketAddress);
// @FIXME: aggregator
const aggregatorAddress = repayReserve.info.liquidity.aggregatorOption
? repayReserve.info.liquidity.aggregator
: withdrawReserve.info.liquidity.aggregator;
const aggregator = cache.get(aggregatorAddress);
if (!dexMarket) {
if (!aggregator) {
throw new Error(`Dex market doesn't exist.`);
}
@ -105,11 +102,11 @@ export const liquidate = async (
withdrawReserve.info.lendingMarket,
) as ParsedAccount<LendingMarket>;
const dexOrderBookSide = market.info.quoteMint.equals(
repayReserve.info.liquidityMint,
const dexOrderBookSide = market.info.quoteTokenMint.equals(
repayReserve.info.liquidity.mint,
)
? dexMarket?.info.asks
: dexMarket?.info.bids;
? aggregator?.info.asks
: aggregator?.info.bids;
const memory = createTempMemoryAccount(
instructions,
@ -119,25 +116,24 @@ export const liquidate = async (
);
instructions.push(
accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey),
// @FIXME: aggregator needed
refreshReserveInstruction(repayReserve.pubkey),
refreshReserveInstruction(withdrawReserve.pubkey),
);
instructions.push(
liquidateInstruction(
amountLamports,
fromAccount,
liquidateObligationInstruction(
liquidityAmount,
sourceAccount,
toAccount,
repayReserve.pubkey,
repayReserve.info.liquiditySupply,
repayReserve.info.liquidity.supply,
withdrawReserve.pubkey,
withdrawReserve.info.collateralSupply,
withdrawReserve.info.collateral.supply,
obligation.pubkey,
repayReserve.info.lendingMarket,
authority,
lendingMarketAuthority,
transferAuthority.publicKey,
dexMarketAddress,
dexOrderBookSide,
memory,
),
);

View File

@ -1,28 +1,35 @@
import {
contexts,
findOrCreateAccountByMint,
LENDING_PROGRAM_ID,
models,
notify,
TokenAccount,
} from '@oyster/common';
import { AccountLayout } from '@solana/spl-token';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import { contexts, utils, actions, models, TokenAccount } from '@oyster/common';
import {
accrueInterestInstruction,
LendingReserve,
withdrawInstruction,
} from './../models/lending';
import { AccountLayout } from '@solana/spl-token';
const { approve } = models;
const { findOrCreateAccountByMint } = actions;
const { sendTransaction } = contexts.Connection;
const { LENDING_PROGRAM_ID, notify } = utils;
redeemReserveCollateralInstruction,
refreshReserveInstruction,
Reserve,
} from '../models';
export const withdraw = async (
from: TokenAccount, // CollateralAccount
amountLamports: number, // in collateral token (lamports)
reserve: LendingReserve,
reserveAddress: PublicKey,
const { approve } = models;
const { sendTransaction } = contexts.Connection;
// @FIXME
export const redeemReserveCollateral = async (
connection: Connection,
wallet: any,
collateralAmount: number,
source: TokenAccount,
reserve: Reserve,
reserveAddress: PublicKey,
) => {
notify({
message: 'Withdrawing funds...',
@ -39,53 +46,57 @@ export const withdraw = async (
AccountLayout.span,
);
const [authority] = await PublicKey.findProgramAddress(
const [lendingMarketAuthority] = await PublicKey.findProgramAddress(
[reserve.lendingMarket.toBuffer()],
LENDING_PROGRAM_ID,
);
const fromAccount = from.pubkey;
const sourceCollateral = source.pubkey;
// create approval for transfer transactions
const transferAuthority = approve(
instructions,
cleanupInstructions,
fromAccount,
sourceCollateral,
wallet.publicKey,
amountLamports,
collateralAmount,
);
signers.push(transferAuthority);
// get destination account
const toAccount = await findOrCreateAccountByMint(
const destinationLiquidity = await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
reserve.liquidityMint,
reserve.liquidity.mint,
signers,
);
instructions.push(accrueInterestInstruction(reserveAddress));
instructions.push(
withdrawInstruction(
amountLamports,
fromAccount,
toAccount,
refreshReserveInstruction(
reserveAddress,
reserve.collateralMint,
reserve.liquiditySupply,
reserve.liquidity.aggregatorOption
? reserve.liquidity.aggregator
: undefined,
),
redeemReserveCollateralInstruction(
collateralAmount,
sourceCollateral,
destinationLiquidity,
reserveAddress,
reserve.collateral.mint,
reserve.liquidity.supply,
reserve.lendingMarket,
authority,
lendingMarketAuthority,
transferAuthority.publicKey,
),
);
try {
let { txid } = await sendTransaction(
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),

View File

@ -1,157 +0,0 @@
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
contexts,
utils,
actions,
models,
ParsedAccount,
TokenAccount,
} from '@oyster/common';
import {
accrueInterestInstruction,
LendingReserve,
} from './../models/lending/reserve';
import { repayInstruction } from './../models/lending/repay';
import { AccountLayout, Token, NATIVE_MINT } from '@solana/spl-token';
import { LendingObligation } from '../models';
const { approve } = models;
const { createTokenAccount, findOrCreateAccountByMint } = actions;
const { sendTransaction } = contexts.Connection;
const { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID, notify } = utils;
export const repay = async (
from: TokenAccount,
repayAmount: number,
// which loan to repay
obligation: ParsedAccount<LendingObligation>,
obligationToken: TokenAccount,
repayReserve: ParsedAccount<LendingReserve>,
withdrawReserve: ParsedAccount<LendingReserve>,
connection: Connection,
wallet: any,
) => {
notify({
message: 'Repaying funds...',
description: 'Please review transactions to approve.',
type: 'warn',
});
// user from account
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
const [authority] = await PublicKey.findProgramAddress(
[repayReserve.info.lendingMarket.toBuffer()],
LENDING_PROGRAM_ID,
);
let fromAccount = from.pubkey;
if (
wallet.publicKey.equals(fromAccount) &&
repayReserve.info.liquidityMint.equals(NATIVE_MINT)
) {
fromAccount = createTokenAccount(
instructions,
wallet.publicKey,
accountRentExempt + repayAmount,
NATIVE_MINT,
wallet.publicKey,
signers,
);
cleanupInstructions.push(
Token.createCloseAccountInstruction(
TOKEN_PROGRAM_ID,
fromAccount,
wallet.publicKey,
wallet.publicKey,
[],
),
);
}
// create approval for transfer transactions
const transferAuthority = approve(
instructions,
cleanupInstructions,
fromAccount,
wallet.publicKey,
repayAmount,
);
signers.push(transferAuthority);
// get destination account
const toAccount = await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
withdrawReserve.info.collateralMint,
signers,
);
// create approval for transfer transactions
approve(
instructions,
cleanupInstructions,
obligationToken.pubkey,
wallet.publicKey,
obligationToken.info.amount.toNumber(),
true,
// reuse transfer authority
transferAuthority.publicKey,
);
instructions.push(
accrueInterestInstruction(repayReserve.pubkey, withdrawReserve.pubkey),
);
instructions.push(
repayInstruction(
repayAmount,
fromAccount,
toAccount,
repayReserve.pubkey,
repayReserve.info.liquiditySupply,
withdrawReserve.pubkey,
withdrawReserve.info.collateralSupply,
obligation.pubkey,
obligation.info.tokenMint,
obligationToken.pubkey,
repayReserve.info.lendingMarket,
authority,
transferAuthority.publicKey,
),
);
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers,
true,
);
notify({
message: 'Funds repaid.',
type: 'success',
description: `Transaction - ${txid}`,
});
};

View File

@ -0,0 +1,124 @@
import {
contexts,
createTokenAccount,
LENDING_PROGRAM_ID,
models,
notify,
ParsedAccount,
TOKEN_PROGRAM_ID,
TokenAccount,
} from '@oyster/common';
import { AccountLayout, NATIVE_MINT, Token } from '@solana/spl-token';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
Obligation,
refreshReserveInstruction,
repayObligationLiquidityInstruction,
Reserve,
} from '../models';
const { approve } = models;
const { sendTransaction } = contexts.Connection;
// @FIXME
export const repayObligationLiquidity = async (
connection: Connection,
wallet: any,
liquidityAmount: number,
source: TokenAccount,
repayReserve: ParsedAccount<Reserve>,
obligation: ParsedAccount<Obligation>,
) => {
notify({
message: 'Repaying funds...',
description: 'Please review transactions to approve.',
type: 'warn',
});
// user from account
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
const [lendingMarketAuthority] = await PublicKey.findProgramAddress(
[repayReserve.info.lendingMarket.toBuffer()],
LENDING_PROGRAM_ID,
);
let sourceLiquidity = source.pubkey;
if (
wallet.publicKey.equals(sourceLiquidity) &&
repayReserve.info.liquidity.mint.equals(NATIVE_MINT)
) {
sourceLiquidity = createTokenAccount(
instructions,
wallet.publicKey,
accountRentExempt + liquidityAmount,
NATIVE_MINT,
wallet.publicKey,
signers,
);
cleanupInstructions.push(
Token.createCloseAccountInstruction(
TOKEN_PROGRAM_ID,
sourceLiquidity,
wallet.publicKey,
wallet.publicKey,
[],
),
);
}
// create approval for transfer transactions
const transferAuthority = approve(
instructions,
cleanupInstructions,
sourceLiquidity,
wallet.publicKey,
liquidityAmount,
);
signers.push(transferAuthority);
instructions.push(
refreshReserveInstruction(
repayReserve.pubkey,
repayReserve.info.liquidity.aggregatorOption
? repayReserve.info.liquidity.aggregator
: undefined,
),
repayObligationLiquidityInstruction(
liquidityAmount,
sourceLiquidity,
repayReserve.info.liquidity.mint,
repayReserve.pubkey,
obligation.pubkey,
repayReserve.info.lendingMarket,
lendingMarketAuthority,
transferAuthority.publicKey,
),
);
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers,
true,
);
notify({
message: 'Funds repaid.',
type: 'success',
description: `Transaction - ${txid}`,
});
};