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

View File

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