feat: add borrow instruction

This commit is contained in:
bartosz-lipinski 2020-11-19 23:41:11 -06:00
parent 5cd9478317
commit d63da050ad
11 changed files with 254 additions and 64 deletions

View File

@ -29,7 +29,7 @@ body {
text-align: center;
display: flex;
align-self: center;
align-items: centerconn;
align-items: center;
height: 100%;
}

View File

@ -1,33 +1,29 @@
import {
Account,
AccountInfo,
Connection,
PublicKey,
sendAndConfirmRawTransaction,
SYSVAR_CLOCK_PUBKEY,
TransactionInstruction,
} from "@solana/web3.js";
import BN from "bn.js";
import * as BufferLayout from "buffer-layout";
import { sendTransaction } from "../contexts/connection";
import { notify } from "../utils/notifications";
import * as Layout from "./../utils/layout";
import { depositInstruction, initReserveInstruction, LendingReserve } from "./../models/lending/reserve";
import { AccountLayout, MintInfo, Token } from "@solana/spl-token";
import { LendingReserve } from "./../models/lending/reserve";
import { AccountLayout, MintInfo, MintLayout, Token } from "@solana/spl-token";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../constants/ids";
import { createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from "./account";
import { cache, GenericAccountParser, MintParser, ParsedAccount } from "../contexts/accounts";
import { TokenAccount } from "../models";
import { isConstructorDeclaration } from "typescript";
import { LendingMarketParser } from "../models/lending";
import { sign } from "crypto";
import { fromLamports, toLamports } from "../utils/utils";
import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
import { TokenAccount, LendingObligationLayout, borrowInstruction, LendingMarket } from "../models";
import { toLamports } from "../utils/utils";
export const borrow = async (
from: TokenAccount,
amount: number,
reserve: LendingReserve,
reserveAddress: PublicKey,
borrowReserve: LendingReserve,
borrowReserveAddress: PublicKey,
depositReserve: LendingReserve,
depositReserveAddress: PublicKey,
connection: Connection,
wallet: any) => {
@ -37,9 +33,6 @@ export const borrow = async (
type: "warn",
});
const isInitalized = true; // TODO: finish reserve init
// user from account
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
@ -49,11 +42,11 @@ export const borrow = async (
);
const [authority] = await PublicKey.findProgramAddress(
[reserve.lendingMarket.toBuffer()], // which account should be authority
[depositReserve.lendingMarket.toBuffer()], // which account should be authority
LENDING_PROGRAM_ID
);
const mint = (await cache.query(connection, reserve.liquidityMint, MintParser)) as ParsedAccount<MintInfo>;
const mint = (await cache.query(connection, depositReserve.collateralMint, MintParser)) as ParsedAccount<MintInfo>;
const amountLamports = toLamports(amount, mint?.info);
const fromAccount = ensureSplAccount(
@ -77,37 +70,77 @@ export const borrow = async (
)
);
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 toAccount = await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
borrowReserve.liquidityMint,
signers
);
const obligation = createUninitializedAccount(
instructions,
wallet.publicKey,
await connection.getMinimumBalanceForRentExemption(
LendingObligationLayout.span
),
signers,
);
const obligationMint = createUninitializedAccount(
instructions,
wallet.publicKey,
await connection.getMinimumBalanceForRentExemption(
MintLayout.span
),
signers,
);
const obligationTokenOutput = createUninitializedAccount(
instructions,
wallet.publicKey,
accountRentExempt,
signers,
);
const market = cache.get(depositReserve.lendingMarket) as ParsedAccount<LendingMarket>;
const dexMarketAddress = market.info.quoteMint.equals(borrowReserve.liquidityMint) ?
borrowReserve.dexMarket :
depositReserve.dexMarket;
const dexMarket = cache.get(dexMarketAddress);
if(!dexMarket) {
throw new Error(`Dex market doesn't exsists.`)
}
const dexOrderBookSide = market.info.quoteMint.equals(borrowReserve.liquidityMint) ?
dexMarket.info.bids :
dexMarket.info.asks;
// deposit
instructions.push(
depositInstruction(
borrowInstruction(
amountLamports,
fromAccount,
toAccount,
depositReserveAddress,
depositReserve.liquiditySupply,
borrowReserveAddress,
borrowReserve.liquiditySupply,
obligation,
obligationMint,
obligationTokenOutput,
wallet.publicKey,
authority,
reserveAddress,
reserve.liquiditySupply,
reserve.collateralMint,
dexMarketAddress,
dexOrderBookSide,
)
);
try {

View File

@ -19,27 +19,43 @@ export const BorrowInput = (props: { className?: string, reserve: LendingReserve
const { id } = useParams<{ id: string }>();
const [value, setValue] = useState('');
const reserve = props.reserve;
const address = props.address;
const borrowReserve = props.reserve;
const borrowReserveAddress = props.address;
const name = useTokenName(reserve?.liquidityMint);
const { balance: tokenBalance, accounts: fromAccounts } = useUserBalance(reserve?.liquidityMint);
const [collateralReserve, setCollateralReserve] = useState<LendingReserve>();
const collateralReserveAddress = useMemo(() => {
return cache.byParser(LendingReserveParser)
.find(acc => cache.get(acc) === collateralReserve);
}, [collateralReserve])
const name = useTokenName(borrowReserve?.liquidityMint);
const {
balance: tokenBalance,
accounts: fromAccounts
} = useUserBalance(collateralReserve?.liquidityMint);
// const collateralBalance = useUserBalance(reserve?.collateralMint);
const onBorrow = useCallback(() => {
if(!collateralReserve || !collateralReserveAddress) {
return;
}
borrow(
fromAccounts[0],
parseFloat(value),
reserve,
address,
borrowReserve,
borrowReserveAddress,
collateralReserve,
new PublicKey(collateralReserveAddress),
connection,
wallet);
}, [value, reserve, fromAccounts, address]);
}, [value, borrowReserve, fromAccounts, borrowReserveAddress]);
const bodyStyle: React.CSSProperties = {
display: 'flex',
const bodyStyle: React.CSSProperties = {
display: 'flex',
flex: 1,
justifyContent: 'center',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
};
@ -51,7 +67,7 @@ export const BorrowInput = (props: { className?: string, reserve: LendingReserve
How much would you like to borrow?
</div>
<div className="token-input">
<TokenIcon mintAddress={reserve?.liquidityMint} />
<TokenIcon mintAddress={borrowReserve?.liquidityMint} />
<NumericInput value={value}
onChange={(val: any) => {
setValue(val);

View File

@ -55,6 +55,7 @@ export const useLending = () => {
.map(acc => [
(acc?.info as LendingReserve).collateralMint.toBase58(),
(acc?.info as LendingReserve).liquidityMint.toBase58(),
(acc?.info as LendingReserve).dexMarket.toBase58(),
])
].flat().filter((p) => p) as string[];

View File

@ -15,11 +15,6 @@ import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
import { useMemo } from "react";
import { EventEmitter } from "./../utils/eventEmitter";
interface RecentPoolData {
pool_identifier: string;
volume24hA: number;
}
export interface MarketsContextState {
midPriceInUSD: (mint: string) => number;
marketEmitter: EventEmitter;

View File

@ -1 +1,2 @@
export * from "./account";
export * from './lending';

View File

@ -0,0 +1,89 @@
import {
PublicKey,
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from "@solana/web3.js";
import BN from "bn.js";
import * as BufferLayout from "buffer-layout";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import * as Layout from "./../../utils/layout";
import { LendingInstruction } from './lending';
/// Borrow tokens from a reserve by depositing collateral tokens. The number of borrowed tokens
/// is calculated by market price. The debt obligation is tokenized.
///
/// 0. `[writable]` Collateral input SPL Token account, $authority can transfer $collateral_amount
/// 1. `[writable]` Liquidity output SPL Token account
/// 2. `[writable]` Deposit reserve account.
/// 3. `[writable]` Deposit reserve collateral supply SPL Token account
/// 4. `[writable]` Borrow reserve account.
/// 5. `[writable]` Borrow reserve liquidity supply SPL Token account
/// 6. `[writable]` Obligation - uninitialized
/// 7. `[writable]` Obligation token mint - uninitialized
/// 8. `[writable]` Obligation token output - uninitialized
/// 9. `[]` Obligation token owner - uninitialized
/// 10 `[]` Derived lending market authority ($authority).
/// 11 `[]` Dex market
/// 12 `[]` Dex order book side // could be bid/ask
/// 13 `[]` Temporary memory
/// 14 `[]` Clock sysvar
/// 15 `[]` Rent sysvar
/// 16 '[]` Token program id
export const borrowInstruction = (
collateralAmount: number | BN,
from: PublicKey, // Collateral input SPL Token account. $authority can transfer $collateralAmount
to: PublicKey, // Liquidity output SPL Token account,
depositReserve: PublicKey,
depositReserveCollateralSupply: PublicKey,
borrowReserve: PublicKey,
borrowReserveLiquiditySupply: PublicKey,
obligation: PublicKey,
obligationMint: PublicKey,
obligationTokenOutput: PublicKey,
obligationTokenOwner: PublicKey,
lendingMarketAuthority: PublicKey,
dexMarket: PublicKey,
dexOrderBookSide: PublicKey,
): TransactionInstruction => {
const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"),
Layout.uint64("collateralAmount"),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: LendingInstruction.BorrowReserveLiquidity,
collateralAmount: new BN(collateralAmount),
},
data
);
const keys = [
{ pubkey: from, isSigner: false, isWritable: true },
{ pubkey: to, isSigner: false, isWritable: true },
{ pubkey: depositReserve, isSigner: false, isWritable: true },
{ pubkey: depositReserveCollateralSupply, isSigner: false, isWritable: true },
{ pubkey: borrowReserve, isSigner: false, isWritable: true },
{ pubkey: borrowReserveLiquiditySupply, isSigner: false, isWritable: false },
{ pubkey: obligation, isSigner: false, isWritable: true },
{ pubkey: obligationMint, isSigner: false, isWritable: true },
{ pubkey: obligationTokenOutput, isSigner: false, isWritable: true },
{ pubkey: obligationTokenOwner, isSigner: false, isWritable: false },
{ pubkey: lendingMarketAuthority, isSigner: false, isWritable: true },
{ pubkey: dexMarket, isSigner: false, isWritable: true },
{ pubkey: dexOrderBookSide, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
];
return new TransactionInstruction({
keys,
programId: LENDING_PROGRAM_ID,
data,
});
};

View File

@ -1,2 +1,5 @@
export * from './market';
export * from './reserve';
export * from './obligation';
export * from './lending';
export * from './borrow';

View File

@ -0,0 +1,8 @@
export enum LendingInstruction {
InitLendingMarket = 0,
InitReserve = 1,
DepositReserveLiquidity = 2,
WithdrawReserveLiquidity = 3,
BorrowReserveLiquidity = 4,
RepayReserveLiquidity = 5
}

View File

@ -1 +1,44 @@
export const x = 1;
import {
AccountInfo,
Connection,
PublicKey,
sendAndConfirmRawTransaction,
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from "@solana/web3.js";
import BN from "bn.js";
import * as BufferLayout from "buffer-layout";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import { sendTransaction } from "../../contexts/connection";
import * as Layout from "./../../utils/layout";
import { LendingInstruction } from './lending';
export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
/// Slot when obligation was updated. Used for calculating interest.
Layout.uint64("lastUpdateSlot"),
/// Amount of collateral tokens deposited for this obligation
Layout.uint64("collateralAmount"),
/// Reserve which collateral tokens were deposited into
Layout.publicKey("collateralSupply"),
/// Borrow rate used for calculating interest.
Layout.uint128("cumulativeBorrowRate"),
/// Amount of tokens borrowed for this obligation plus interest
Layout.uint128("borrowAmount"),
/// Reserve which tokens were borrowed from
Layout.publicKey("borrowReserve"),
/// Mint address of the tokens for this obligation
Layout.publicKey("tokenMint"),
]
);
export interface LendingObligation {
lastUpdateSlot: BN;
collateralAmount: BN;
collateralSupply: PublicKey;
cumulativeBorrowRate: BN; // decimals
borrowAmount: BN; // decimals
borrowReserve: PublicKey;
tokenMint: PublicKey;
}

View File

@ -12,6 +12,7 @@ import * as BufferLayout from "buffer-layout";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import { sendTransaction } from "../../contexts/connection";
import * as Layout from "./../../utils/layout";
import { LendingInstruction } from './lending';
export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
@ -108,7 +109,7 @@ export const initReserveInstruction = (
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 1, // Init reserve instruction
instruction: LendingInstruction.InitReserve, // Init reserve instruction
liquidityAmount: new BN(liquidityAmount),
maxUtilizationRate: maxUtilizationRate,
},
@ -169,7 +170,7 @@ export const depositInstruction = (
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 2, // Deposit instruction
instruction: LendingInstruction.DepositReserveLiquidity, // Deposit instruction
liquidityAmount: new BN(liquidityAmount),
},
data