mirror of https://github.com/certusone/oyster.git
actions
This commit is contained in:
parent
c4be0d78e5
commit
b9a71a423a
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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,
|
||||
}),
|
||||
);
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -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),
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
@ -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),
|
|
@ -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}`,
|
||||
});
|
||||
};
|
|
@ -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}`,
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue