feat: borrow instruction
This commit is contained in:
parent
3554e8ce86
commit
42af776ee8
|
@ -44,6 +44,31 @@ export function ensureSplAccount(
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_TEMP_MEM_SPACE = 65528;
|
||||||
|
|
||||||
|
export function createTempMemoryAccount(
|
||||||
|
instructions: TransactionInstruction[],
|
||||||
|
payer: PublicKey,
|
||||||
|
signers: Account[],
|
||||||
|
space = DEFAULT_TEMP_MEM_SPACE) {
|
||||||
|
const account = new Account();
|
||||||
|
instructions.push(
|
||||||
|
SystemProgram.createAccount({
|
||||||
|
fromPubkey: payer,
|
||||||
|
newAccountPubkey: account.publicKey,
|
||||||
|
// 0 will evict/clost account since it cannot pay rent
|
||||||
|
lamports: 0,
|
||||||
|
space: space,
|
||||||
|
programId: TOKEN_PROGRAM_ID,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
signers.push(account);
|
||||||
|
|
||||||
|
return account.publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function createUninitializedAccount(
|
export function createUninitializedAccount(
|
||||||
instructions: TransactionInstruction[],
|
instructions: TransactionInstruction[],
|
||||||
payer: PublicKey,
|
payer: PublicKey,
|
||||||
|
|
|
@ -9,10 +9,11 @@ import { notify } from "../utils/notifications";
|
||||||
import { LendingReserve } from "./../models/lending/reserve";
|
import { LendingReserve } from "./../models/lending/reserve";
|
||||||
import { AccountLayout, MintInfo, MintLayout, Token } from "@solana/spl-token";
|
import { AccountLayout, MintInfo, MintLayout, Token } from "@solana/spl-token";
|
||||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../constants/ids";
|
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../constants/ids";
|
||||||
import { createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from "./account";
|
import { createTempMemoryAccount, createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from "./account";
|
||||||
import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
|
import { cache, MintParser, ParsedAccount } from "../contexts/accounts";
|
||||||
import { TokenAccount, LendingObligationLayout, borrowInstruction, LendingMarket } from "../models";
|
import { TokenAccount, LendingObligationLayout, borrowInstruction, LendingMarket } from "../models";
|
||||||
import { toLamports } from "../utils/utils";
|
import { toLamports } from "../utils/utils";
|
||||||
|
import { DexMarketParser } from "../models/dex";
|
||||||
|
|
||||||
export const borrow = async (
|
export const borrow = async (
|
||||||
from: TokenAccount,
|
from: TokenAccount,
|
||||||
|
@ -33,14 +34,68 @@ export const borrow = async (
|
||||||
type: "warn",
|
type: "warn",
|
||||||
});
|
});
|
||||||
|
|
||||||
const signers: Account[] = [];
|
let signers: Account[] = [];
|
||||||
const instructions: TransactionInstruction[] = [];
|
let instructions: TransactionInstruction[] = [];
|
||||||
const cleanupInstructions: TransactionInstruction[] = [];
|
let cleanupInstructions: TransactionInstruction[] = [];
|
||||||
|
|
||||||
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||||
AccountLayout.span
|
AccountLayout.span
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
let toAccount = await findOrCreateAccountByMint(
|
||||||
|
wallet.publicKey,
|
||||||
|
wallet.publicKey,
|
||||||
|
instructions,
|
||||||
|
cleanupInstructions,
|
||||||
|
accountRentExempt,
|
||||||
|
borrowReserve.liquidityMint,
|
||||||
|
signers
|
||||||
|
);
|
||||||
|
|
||||||
|
// create all accounts in one transaction
|
||||||
|
let tx = await sendTransaction(connection, wallet, instructions, [...signers]);
|
||||||
|
|
||||||
|
notify({
|
||||||
|
message: "Obligation accounts created",
|
||||||
|
description: `Transaction ${tx}`,
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
notify({
|
||||||
|
message: "Adding Liquidity...",
|
||||||
|
description: "Please review transactions to approve.",
|
||||||
|
type: "warn",
|
||||||
|
});
|
||||||
|
|
||||||
|
signers = [];
|
||||||
|
instructions = [];
|
||||||
|
cleanupInstructions = [];
|
||||||
|
|
||||||
const [authority] = await PublicKey.findProgramAddress(
|
const [authority] = await PublicKey.findProgramAddress(
|
||||||
[depositReserve.lendingMarket.toBuffer()], // which account should be authority
|
[depositReserve.lendingMarket.toBuffer()], // which account should be authority
|
||||||
LENDING_PROGRAM_ID
|
LENDING_PROGRAM_ID
|
||||||
|
@ -70,56 +125,25 @@ export const borrow = async (
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
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 market = cache.get(depositReserve.lendingMarket) as ParsedAccount<LendingMarket>;
|
||||||
|
|
||||||
|
const dexMarketAddress = borrowReserve.dexMarketOption ? borrowReserve.dexMarket : depositReserve.dexMarket;
|
||||||
const dexMarketAddress = market.info.quoteMint.equals(borrowReserve.liquidityMint) ?
|
|
||||||
borrowReserve.dexMarket :
|
|
||||||
depositReserve.dexMarket;
|
|
||||||
|
|
||||||
const dexMarket = cache.get(dexMarketAddress);
|
const dexMarket = cache.get(dexMarketAddress);
|
||||||
|
debugger;
|
||||||
|
|
||||||
if(!dexMarket) {
|
if(!dexMarket) {
|
||||||
throw new Error(`Dex market doesn't exsists.`)
|
throw new Error(`Dex market doesn't exsists.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dexOrderBookSide = market.info.quoteMint.equals(borrowReserve.liquidityMint) ?
|
const dexOrderBookSide = market.info.quoteMint.equals(depositReserve.liquidityMint) ?
|
||||||
dexMarket.info.bids :
|
dexMarket?.info.bids :
|
||||||
dexMarket.info.asks;
|
dexMarket?.info.asks
|
||||||
|
|
||||||
|
const memory = createTempMemoryAccount(
|
||||||
|
instructions,
|
||||||
|
wallet.publicKey,
|
||||||
|
signers,
|
||||||
|
);
|
||||||
|
|
||||||
// deposit
|
// deposit
|
||||||
instructions.push(
|
instructions.push(
|
||||||
|
@ -128,7 +152,7 @@ export const borrow = async (
|
||||||
fromAccount,
|
fromAccount,
|
||||||
toAccount,
|
toAccount,
|
||||||
depositReserveAddress,
|
depositReserveAddress,
|
||||||
depositReserve.liquiditySupply,
|
depositReserve.collateralSupply,
|
||||||
borrowReserveAddress,
|
borrowReserveAddress,
|
||||||
borrowReserve.liquiditySupply,
|
borrowReserve.liquiditySupply,
|
||||||
|
|
||||||
|
@ -141,6 +165,8 @@ export const borrow = async (
|
||||||
|
|
||||||
dexMarketAddress,
|
dexMarketAddress,
|
||||||
dexOrderBookSide,
|
dexOrderBookSide,
|
||||||
|
|
||||||
|
memory,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useMemo, useState } from "react";
|
||||||
import { useCollateralBalance, useLendingReserve, useLendingReserves, useTokenName, useUserAccounts, useUserBalance } from '../../hooks';
|
import { useCollateralBalance, useLendingReserves, useTokenName, useUserBalance } from '../../hooks';
|
||||||
import { LendingReserve, LendingReserveParser } from "../../models";
|
import { LendingReserve, LendingReserveParser } from "../../models";
|
||||||
import { TokenIcon } from "../TokenIcon";
|
import { TokenIcon } from "../TokenIcon";
|
||||||
import { formatNumber, getTokenName } from "../../utils/utils";
|
import { getTokenName } from "../../utils/utils";
|
||||||
import { Button, Card, Select } from "antd";
|
import { Button, Card, Select } from "antd";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { cache, ParsedAccount, useAccount } from "../../contexts/accounts";
|
import { cache, ParsedAccount } from "../../contexts/accounts";
|
||||||
import { NumericInput } from "../Input/numeric";
|
import { NumericInput } from "../Input/numeric";
|
||||||
import { useConnection, useConnectionConfig } from "../../contexts/connection";
|
import { useConnection, useConnectionConfig } from "../../contexts/connection";
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useWallet } from "../../contexts/wallet";
|
||||||
import { borrow } from '../../actions';
|
import { borrow } from '../../actions';
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from "@solana/web3.js";
|
||||||
import './style.less';
|
import './style.less';
|
||||||
import { Token } from "@solana/spl-token";
|
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
|
@ -55,7 +54,6 @@ const CollateralSelector = (props: {
|
||||||
export const BorrowInput = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => {
|
export const BorrowInput = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => {
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const { id } = useParams<{ id: string }>();
|
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
const borrowReserve = props.reserve;
|
const borrowReserve = props.reserve;
|
||||||
|
@ -71,26 +69,18 @@ export const BorrowInput = (props: { className?: string, reserve: LendingReserve
|
||||||
return cache.get(id) as ParsedAccount<LendingReserve>;
|
return cache.get(id) as ParsedAccount<LendingReserve>;
|
||||||
}, [collateralReserveMint])
|
}, [collateralReserveMint])
|
||||||
|
|
||||||
const collateral = useCollateralBalance(collateralReserve?.info);
|
|
||||||
|
|
||||||
const name = useTokenName(borrowReserve?.liquidityMint);
|
const name = useTokenName(borrowReserve?.liquidityMint);
|
||||||
const {
|
const {
|
||||||
balance: tokenBalance,
|
|
||||||
accounts: fromAccounts
|
accounts: fromAccounts
|
||||||
} = useUserBalance(collateralReserve?.info.collateralMint);
|
} = useUserBalance(collateralReserve?.info.collateralMint);
|
||||||
// const collateralBalance = useUserBalance(reserve?.collateralMint);
|
// const collateralBalance = useUserBalance(reserve?.collateralMint);
|
||||||
|
|
||||||
if(collateral) {
|
|
||||||
debugger;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onBorrow = useCallback(() => {
|
const onBorrow = useCallback(() => {
|
||||||
if (!collateralReserve) {
|
if (!collateralReserve) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugger;
|
|
||||||
|
|
||||||
borrow(
|
borrow(
|
||||||
fromAccounts[0],
|
fromAccounts[0],
|
||||||
parseFloat(value),
|
parseFloat(value),
|
||||||
|
|
|
@ -42,6 +42,7 @@ export const DepositInput = (props: { className?: string, reserve: LendingReserv
|
||||||
console.log(`liquidityMint: ${reserve.liquidityMint.toBase58()}`);
|
console.log(`liquidityMint: ${reserve.liquidityMint.toBase58()}`);
|
||||||
console.log(`collateralSupply: ${reserve.collateralSupply.toBase58()}`);
|
console.log(`collateralSupply: ${reserve.collateralSupply.toBase58()}`);
|
||||||
console.log(`collateralMint: ${reserve.collateralMint.toBase58()}`);
|
console.log(`collateralMint: ${reserve.collateralMint.toBase58()}`);
|
||||||
|
console.log(`dexMarket: ${reserve.dexMarket.toBase58()}`);
|
||||||
})();
|
})();
|
||||||
}, [reserve])
|
}, [reserve])
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ export interface ParsedAccountBase {
|
||||||
export type AccountParser = (
|
export type AccountParser = (
|
||||||
pubkey: PublicKey,
|
pubkey: PublicKey,
|
||||||
data: AccountInfo<Buffer>
|
data: AccountInfo<Buffer>
|
||||||
) => ParsedAccountBase;
|
) => ParsedAccountBase | undefined;
|
||||||
|
|
||||||
export interface ParsedAccount<T> extends ParsedAccountBase {
|
export interface ParsedAccount<T> extends ParsedAccountBase {
|
||||||
info: T;
|
info: T;
|
||||||
|
@ -47,7 +47,7 @@ export const MintParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
export const TokenAccountParser = (pubKey: PublicKey, info: AccountInfo<Buffer>) => {
|
||||||
const buffer = Buffer.from(info.data);
|
const buffer = Buffer.from(info.data);
|
||||||
const data = deserializeAccount(buffer);
|
const data = deserializeAccount(buffer);
|
||||||
|
|
||||||
const details = {
|
const details = {
|
||||||
|
@ -130,6 +130,10 @@ export const cache = {
|
||||||
cache.registerParser(id, deserialize);
|
cache.registerParser(id, deserialize);
|
||||||
pendingCalls.delete(address);
|
pendingCalls.delete(address);
|
||||||
const account = deserialize(new PublicKey(address), obj);
|
const account = deserialize(new PublicKey(address), obj);
|
||||||
|
if (!account) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
genericCache.set(address, account);
|
genericCache.set(address, account);
|
||||||
cache.emitter.raiseCacheUpdated(address, deserialize);
|
cache.emitter.raiseCacheUpdated(address, deserialize);
|
||||||
return account;
|
return account;
|
||||||
|
@ -146,8 +150,8 @@ export const cache = {
|
||||||
},
|
},
|
||||||
byParser: (parser: AccountParser) => {
|
byParser: (parser: AccountParser) => {
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
for(const id of keyToAccountParser.keys()) {
|
for (const id of keyToAccountParser.keys()) {
|
||||||
if(keyToAccountParser.get(id) === parser) {
|
if (keyToAccountParser.get(id) === parser) {
|
||||||
result.push(id);
|
result.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,8 +159,12 @@ export const cache = {
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
|
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
|
||||||
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
|
if (pubkey) {
|
||||||
keyToAccountParser.set(address, parser);
|
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
|
||||||
|
keyToAccountParser.set(address, parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubkey;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -200,7 +208,7 @@ const UseNativeAccount = () => {
|
||||||
|
|
||||||
const updateCache = useCallback((account) => {
|
const updateCache = useCallback((account) => {
|
||||||
const wrapped = wrapNativeAccount(wallet.publicKey, account);
|
const wrapped = wrapNativeAccount(wallet.publicKey, account);
|
||||||
if(wrapped !== undefined && wallet) {
|
if (wrapped !== undefined && wallet) {
|
||||||
cache.registerParser(wallet.publicKey.toBase58(), TokenAccountParser);
|
cache.registerParser(wallet.publicKey.toBase58(), TokenAccountParser);
|
||||||
genericCache.set(wallet.publicKey.toBase58(), wrapped as TokenAccount);
|
genericCache.set(wallet.publicKey.toBase58(), wrapped as TokenAccount);
|
||||||
}
|
}
|
||||||
|
@ -352,7 +360,7 @@ export const getMultipleAccounts = async (
|
||||||
.map(
|
.map(
|
||||||
(a) =>
|
(a) =>
|
||||||
a.array.map((acc) => {
|
a.array.map((acc) => {
|
||||||
if(!acc) {
|
if (!acc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,16 +12,20 @@ import { notify } from "./../utils/notifications";
|
||||||
import { ExplorerLink } from "../components/ExplorerLink";
|
import { ExplorerLink } from "../components/ExplorerLink";
|
||||||
import LocalTokens from '../config/tokens.json';
|
import LocalTokens from '../config/tokens.json';
|
||||||
|
|
||||||
export type ENV = "mainnet-beta" | "testnet" | "devnet" | "localnet";
|
export type ENV = "mainnet-beta" | "testnet" | "devnet" | "localnet" | "lending";
|
||||||
|
|
||||||
export const ENDPOINTS = [
|
export const ENDPOINTS = [
|
||||||
|
{
|
||||||
|
name: 'lending' as ENV,
|
||||||
|
endpoint: "https://tln.solana.com",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "mainnet-beta" as ENV,
|
name: "mainnet-beta" as ENV,
|
||||||
endpoint: "https://solana-api.projectserum.com/",
|
endpoint: "https://solana-api.projectserum.com/",
|
||||||
},
|
},
|
||||||
{ name: "testnet" as ENV, endpoint: clusterApiUrl("testnet") },
|
{ name: "testnet" as ENV, endpoint: clusterApiUrl("testnet") },
|
||||||
{ name: "devnet" as ENV, endpoint: clusterApiUrl("devnet") },
|
{ name: "devnet" as ENV, endpoint: clusterApiUrl("devnet") },
|
||||||
{ name: "localnet" as ENV, endpoint: "http://35.206.228.142:8899" },
|
{ name: "localnet" as ENV, endpoint: "http://127.0.0.1:8899" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT = ENDPOINTS[0].endpoint;
|
const DEFAULT = ENDPOINTS[0].endpoint;
|
||||||
|
|
|
@ -2,8 +2,9 @@ import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useConnection } from "./connection";
|
import { useConnection } from "./connection";
|
||||||
import { LENDING_PROGRAM_ID } from "./../constants/ids";
|
import { LENDING_PROGRAM_ID } from "./../constants/ids";
|
||||||
import { LendingMarketParser, isLendingReserve, isLendingMarket, LendingReserveParser, LendingReserve } from "./../models/lending";
|
import { LendingMarketParser, isLendingReserve, isLendingMarket, LendingReserveParser, LendingReserve } from "./../models/lending";
|
||||||
import { cache, getMultipleAccounts } from "./accounts";
|
import { cache, getMultipleAccounts, MintParser, ParsedAccount } from "./accounts";
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
import { DexMarketParser } from "../models/dex";
|
||||||
|
|
||||||
export interface LendingContextState {
|
export interface LendingContextState {
|
||||||
|
|
||||||
|
@ -51,20 +52,25 @@ export const useLending = () => {
|
||||||
|
|
||||||
const toQuery = [
|
const toQuery = [
|
||||||
...accounts.filter(acc => (acc?.info as LendingReserve).lendingMarket !== undefined)
|
...accounts.filter(acc => (acc?.info as LendingReserve).lendingMarket !== undefined)
|
||||||
.map(acc => [
|
.map(acc => acc as ParsedAccount<LendingReserve>)
|
||||||
(acc?.info as LendingReserve).collateralMint.toBase58(),
|
.map(acc => {
|
||||||
(acc?.info as LendingReserve).liquidityMint.toBase58(),
|
const result = [
|
||||||
(acc?.info as LendingReserve).dexMarket.toBase58(),
|
cache.registerParser(acc?.info.collateralMint.toBase58(), MintParser),
|
||||||
])
|
cache.registerParser(acc?.info.liquidityMint.toBase58(), MintParser),
|
||||||
].flat().filter((p) => p) as string[];
|
// ignore dex if its not set
|
||||||
|
cache.registerParser(acc?.info.dexMarketOption ? acc?.info.dexMarket.toBase58() : '', DexMarketParser),
|
||||||
|
].filter(_ => _);
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
].flat() as string[];
|
||||||
|
|
||||||
// This will pre-cache all accounts used by pools
|
// This will pre-cache all accounts used by pools
|
||||||
// All those accounts are updated whenever there is a change
|
// All those accounts are updated whenever there is a change
|
||||||
await getMultipleAccounts(connection, toQuery, "single").then(
|
await getMultipleAccounts(connection, toQuery, "single").then(
|
||||||
({ keys, array }) => {
|
({ keys, array }) => {
|
||||||
return array.map((obj, index) => {
|
return array.map((obj, index) => {
|
||||||
// TODO: add to cache
|
const address = keys[index];
|
||||||
|
cache.add(address, obj);
|
||||||
return obj;
|
return obj;
|
||||||
}) as any[];
|
}) as any[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { EventEmitter } from "./../utils/eventEmitter";
|
import { EventEmitter } from "./../utils/eventEmitter";
|
||||||
|
|
||||||
|
import { DexMarketParser } from "./../models/dex";
|
||||||
|
|
||||||
export interface MarketsContextState {
|
export interface MarketsContextState {
|
||||||
midPriceInUSD: (mint: string) => number;
|
midPriceInUSD: (mint: string) => number;
|
||||||
marketEmitter: EventEmitter;
|
marketEmitter: EventEmitter;
|
||||||
|
@ -39,7 +41,9 @@ export function MarketProvider({ children = null as any }) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// TODO: identify which markets to query ...
|
// TODO: identify which markets to query ...
|
||||||
const mints = useMemo(() => [] as PublicKey[], []);
|
const mints = useMemo(() => [
|
||||||
|
|
||||||
|
] as PublicKey[], []);
|
||||||
|
|
||||||
const marketByMint = useMemo(() => {
|
const marketByMint = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
@ -107,24 +111,7 @@ export function MarketProvider({ children = null as any }) {
|
||||||
if (market) {
|
if (market) {
|
||||||
const programId = market.marketInfo.programId;
|
const programId = market.marketInfo.programId;
|
||||||
const id = market.marketInfo.address;
|
const id = market.marketInfo.address;
|
||||||
cache.add(id, item, (id, acc) => {
|
cache.add(id, item, DexMarketParser);
|
||||||
const decoded = Market.getLayout(programId).decode(acc.data);
|
|
||||||
|
|
||||||
const details = {
|
|
||||||
pubkey: id,
|
|
||||||
account: {
|
|
||||||
...acc,
|
|
||||||
},
|
|
||||||
info: decoded,
|
|
||||||
} as ParsedAccountBase;
|
|
||||||
|
|
||||||
cache.registerParser(details.info.baseMint, MintParser);
|
|
||||||
cache.registerParser(details.info.quoteMint, MintParser);
|
|
||||||
cache.registerParser(details.info.bids, OrderBookParser);
|
|
||||||
cache.registerParser(details.info.asks, OrderBookParser);
|
|
||||||
|
|
||||||
return details;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,19 +243,7 @@ export const useMidPriceInUSD = (mint: string) => {
|
||||||
return { price, isBase: price === 1.0 };
|
return { price, isBase: price === 1.0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
const OrderBookParser = (id: PublicKey, acc: AccountInfo<Buffer>) => {
|
|
||||||
const decoded = Orderbook.LAYOUT.decode(acc.data);
|
|
||||||
|
|
||||||
const details = {
|
|
||||||
pubkey: id,
|
|
||||||
account: {
|
|
||||||
...acc,
|
|
||||||
},
|
|
||||||
info: decoded,
|
|
||||||
} as ParsedAccountBase;
|
|
||||||
|
|
||||||
return details;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
|
||||||
const SERUM_TOKEN = TOKEN_MINTS.find(
|
const SERUM_TOKEN = TOKEN_MINTS.find(
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useContext } from "react";
|
|
||||||
import { TokenAccount } from "../models";
|
import { TokenAccount } from "../models";
|
||||||
import { useAccountsContext } from './../contexts/accounts';
|
import { useAccountsContext } from './../contexts/accounts';
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,12 @@ export function useUserBalance(mint?: PublicKey) {
|
||||||
return userAccounts
|
return userAccounts
|
||||||
.filter(acc => mint?.equals(acc.info.mint))
|
.filter(acc => mint?.equals(acc.info.mint))
|
||||||
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
|
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
|
||||||
}, [userAccounts]);
|
}, [userAccounts, mint]);
|
||||||
|
|
||||||
const balanceLamports = useMemo(() => {
|
const balanceLamports = useMemo(() => {
|
||||||
return accounts
|
return accounts
|
||||||
.reduce((res, item) => res += item.info.amount.toNumber(), 0);
|
.reduce((res, item) => res += item.info.amount.toNumber(), 0);
|
||||||
},[accounts, mintInfo]);
|
},[accounts]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
balance: fromLamports(balanceLamports, mintInfo),
|
balance: fromLamports(balanceLamports, mintInfo),
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./market";
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Market, MARKETS, Orderbook } from "@project-serum/serum";
|
||||||
|
import { AccountInfo, PublicKey } from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
MintParser,
|
||||||
|
ParsedAccountBase,
|
||||||
|
cache,
|
||||||
|
} from "./../../contexts/accounts";
|
||||||
|
|
||||||
|
export const OrderBookParser = (id: PublicKey, acc: AccountInfo<Buffer>) => {
|
||||||
|
const decoded = Orderbook.LAYOUT.decode(acc.data);
|
||||||
|
|
||||||
|
const details = {
|
||||||
|
pubkey: id,
|
||||||
|
account: {
|
||||||
|
...acc,
|
||||||
|
},
|
||||||
|
info: decoded,
|
||||||
|
} as ParsedAccountBase;
|
||||||
|
|
||||||
|
return details;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_DEX_ID = new PublicKey('EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o');
|
||||||
|
|
||||||
|
export const DexMarketParser = (pubkey: PublicKey, acc: AccountInfo<Buffer>) => {
|
||||||
|
const market = MARKETS.find(m => m.address.equals(pubkey));
|
||||||
|
const decoded = Market.getLayout(market?.programId || DEFAULT_DEX_ID)
|
||||||
|
.decode(acc.data);
|
||||||
|
|
||||||
|
const details = {
|
||||||
|
pubkey,
|
||||||
|
account: {
|
||||||
|
...acc,
|
||||||
|
},
|
||||||
|
info: decoded,
|
||||||
|
} as ParsedAccountBase;
|
||||||
|
|
||||||
|
cache.registerParser(details.info.baseMint, MintParser);
|
||||||
|
cache.registerParser(details.info.quoteMint, MintParser);
|
||||||
|
cache.registerParser(details.info.bids, OrderBookParser);
|
||||||
|
cache.registerParser(details.info.asks, OrderBookParser);
|
||||||
|
|
||||||
|
return details;
|
||||||
|
}
|
|
@ -48,12 +48,15 @@ export const borrowInstruction = (
|
||||||
|
|
||||||
dexMarket: PublicKey,
|
dexMarket: PublicKey,
|
||||||
dexOrderBookSide: PublicKey,
|
dexOrderBookSide: PublicKey,
|
||||||
|
|
||||||
|
memory: PublicKey,
|
||||||
): TransactionInstruction => {
|
): TransactionInstruction => {
|
||||||
const dataLayout = BufferLayout.struct([
|
const dataLayout = BufferLayout.struct([
|
||||||
BufferLayout.u8("instruction"),
|
BufferLayout.u8("instruction"),
|
||||||
Layout.uint64("collateralAmount"),
|
Layout.uint64("collateralAmount"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
debugger;
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
dataLayout.encode(
|
dataLayout.encode(
|
||||||
{
|
{
|
||||||
|
@ -77,6 +80,7 @@ export const borrowInstruction = (
|
||||||
{ pubkey: lendingMarketAuthority, isSigner: false, isWritable: true },
|
{ pubkey: lendingMarketAuthority, isSigner: false, isWritable: true },
|
||||||
{ pubkey: dexMarket, isSigner: false, isWritable: true },
|
{ pubkey: dexMarket, isSigner: false, isWritable: true },
|
||||||
{ pubkey: dexOrderBookSide, isSigner: false, isWritable: false },
|
{ pubkey: dexOrderBookSide, isSigner: false, isWritable: false },
|
||||||
|
{ pubkey: memory, isSigner: false, isWritable: true },
|
||||||
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
||||||
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
||||||
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
||||||
|
|
|
@ -16,6 +16,7 @@ export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.
|
||||||
Layout.uint64("lastUpdateSlot"),
|
Layout.uint64("lastUpdateSlot"),
|
||||||
Layout.publicKey("lendingMarket"),
|
Layout.publicKey("lendingMarket"),
|
||||||
Layout.publicKey("liquidityMint"),
|
Layout.publicKey("liquidityMint"),
|
||||||
|
BufferLayout.u8("liquidityMintDecimals"),
|
||||||
Layout.publicKey("liquiditySupply"),
|
Layout.publicKey("liquiditySupply"),
|
||||||
Layout.publicKey("collateralMint"),
|
Layout.publicKey("collateralMint"),
|
||||||
Layout.publicKey("collateralSupply"),
|
Layout.publicKey("collateralSupply"),
|
||||||
|
@ -23,9 +24,16 @@ export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.
|
||||||
BufferLayout.u32('dexMarketOption'),
|
BufferLayout.u32('dexMarketOption'),
|
||||||
Layout.publicKey("dexMarket"),
|
Layout.publicKey("dexMarket"),
|
||||||
|
|
||||||
BufferLayout.u8("maxUtilizationRate"),
|
BufferLayout.struct([
|
||||||
|
/// Max utilization rate as a percent
|
||||||
BufferLayout.u8("collateralFactor"),
|
BufferLayout.u8("maxUtilizationRate"),
|
||||||
|
/// The ratio of the loan to the value of the collateral as a percent
|
||||||
|
BufferLayout.u8("loanToValueRatio"),
|
||||||
|
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
|
||||||
|
BufferLayout.u8("liquidationBonus"),
|
||||||
|
/// The percent at which an obligation is considered unhealthy
|
||||||
|
BufferLayout.u8("liquidationThreshold"),
|
||||||
|
], "config"),
|
||||||
|
|
||||||
Layout.uint128("cumulativeBorrowRate"),
|
Layout.uint128("cumulativeBorrowRate"),
|
||||||
Layout.uint128("totalBorrows"),
|
Layout.uint128("totalBorrows"),
|
||||||
|
@ -36,6 +44,8 @@ export const LendingReserveLayout: typeof BufferLayout.Structure = BufferLayout.
|
||||||
);
|
);
|
||||||
|
|
||||||
export const isLendingReserve = (info: AccountInfo<Buffer>) => {
|
export const isLendingReserve = (info: AccountInfo<Buffer>) => {
|
||||||
|
console.log(LendingReserveLayout.span);
|
||||||
|
console.log(info.data.length);
|
||||||
return info.data.length === LendingReserveLayout.span;
|
return info.data.length === LendingReserveLayout.span;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue