mango-client-ts/src/client.ts

1526 lines
51 KiB
TypeScript

import {
Account,
Connection,
PublicKey,
sendAndConfirmRawTransaction,
SimulatedTransactionResponse,
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
Transaction, TransactionConfirmationStatus,
TransactionInstruction,
TransactionSignature,
} from '@solana/web3.js';
import {
encodeMangoInstruction,
MangoGroupLayout, MangoSrmAccountLayout,
MarginAccountLayout,
MAX_RATE,
NUM_MARKETS,
NUM_TOKENS,
OPTIMAL_RATE,
OPTIMAL_UTIL,
WideBits,
} from './layout';
import BN from 'bn.js';
import {
awaitTransactionSignatureConfirmation,
createAccountInstruction,
getFilteredProgramAccounts,
getUnixTs,
nativeToUi,
parseTokenAccountData,
promiseUndef,
simulateTransaction,
sleep,
uiToNative,
zeroKey,
} from './utils';
import {
getFeeRates,
getFeeTier,
Market,
OpenOrders,
Orderbook,
TOKEN_MINTS
} from '@project-serum/serum';
import { SRM_DECIMALS } from '@project-serum/serum/lib/token-instructions';
import { Order } from '@project-serum/serum/lib/market';
import Wallet from '@project-serum/sol-wallet-adapter';
import {
makeAddMarginAccountInfoInstruction,
makeBorrowInstruction,
makeCancelOrderInstruction,
makeForceCancelOrdersInstruction, makePartialLiquidateInstruction,
makeSettleFundsInstruction,
makeWithdrawInstruction,
} from './instruction';
import { Aggregator } from './schema';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { getMultipleAccounts } from '.';
export const tokenToDecimals = {
"BTC": 4,
"ETH": 3,
"SOL": 1,
"SRM": 1,
"USDT": 2,
"USDC": 2,
"WUSDT": 2,
}
export class MangoGroup {
publicKey: PublicKey;
accountFlags!: WideBits;
tokens!: PublicKey[];
vaults!: PublicKey[];
indexes!: { lastUpdate: BN, borrow: number, deposit: number };
spotMarkets!: PublicKey[];
oracles!: PublicKey[];
signerNonce!: BN;
signerKey!: PublicKey;
dexProgramId!: PublicKey;
totalDeposits!: number[];
totalBorrows!: number[];
maintCollRatio!: number;
initCollRatio!: number;
srmVault!: PublicKey;
admin!: PublicKey;
borrowLimits!: number[];
mintDecimals!: number[];
oracleDecimals!: number[];
nativeSrm: number | null;
constructor(publicKey: PublicKey, decoded: any, nativeSrm?: number) {
this.publicKey = publicKey
Object.assign(this, decoded)
if (nativeSrm) {
this.nativeSrm = nativeSrm
} else {
this.nativeSrm = null
}
}
async getPrices(
connection: Connection,
): Promise<number[]> {
const aggs = await Promise.all(this.oracles.map((pk) => (Aggregator.loadWithConnection(pk, connection))))
return aggs.map((agg) => (agg.answer.median.toNumber() / Math.pow(10, agg.config.decimals))).concat(1.0)
}
getMarketIndex(spotMarket: Market): number {
for (let i = 0; i < this.spotMarkets.length; i++) {
if (this.spotMarkets[i].equals(spotMarket.publicKey)) {
return i
}
}
throw new Error("This Market does not belong to this MangoGroup")
}
getTokenIndex(token: PublicKey): number {
for (let i = 0; i < this.tokens.length; i++) {
if (this.tokens[i].equals(token)) {
return i
}
}
throw new Error("This token does not belong in this MangoGroup")
}
getBorrowRate(tokenIndex: number): number {
const totalBorrows = this.getUiTotalBorrow(tokenIndex)
const totalDeposits = this.getUiTotalDeposit(tokenIndex)
if (totalDeposits === 0 && totalBorrows === 0) {
return 0
}
if (totalDeposits <= totalBorrows) {
return MAX_RATE
}
const utilization = totalBorrows / totalDeposits
if (utilization > OPTIMAL_UTIL) {
const extraUtil = utilization - OPTIMAL_UTIL
const slope = (MAX_RATE - OPTIMAL_RATE) / (1 - OPTIMAL_UTIL)
return OPTIMAL_RATE + slope * extraUtil
} else {
const slope = OPTIMAL_RATE / OPTIMAL_UTIL
return slope * utilization
}
}
getDepositRate(tokenIndex: number): number {
const borrowRate = this.getBorrowRate(tokenIndex)
const totalBorrows = this.getUiTotalBorrow(tokenIndex)
const totalDeposits = this.getUiTotalDeposit(tokenIndex)
if (totalDeposits === 0 && totalBorrows === 0) {
return 0
} else if (totalDeposits === 0) {
return MAX_RATE
}
const utilization = totalBorrows / totalDeposits
return utilization * borrowRate
}
getUiTotalDeposit(tokenIndex: number): number {
return nativeToUi(this.totalDeposits[tokenIndex] * this.indexes[tokenIndex].deposit, this.mintDecimals[tokenIndex])
}
getUiTotalBorrow(tokenIndex: number): number {
return nativeToUi(this.totalBorrows[tokenIndex] * this.indexes[tokenIndex].borrow, this.mintDecimals[tokenIndex])
}
getTokenSymbol(tokenIndex: number): string {
return TOKEN_MINTS.find((m) => m.address.toString() === this.tokens[tokenIndex].toString())?.name || '';
}
getTokenDecimals(tokenIndex: number): number {
return tokenToDecimals[this.getTokenSymbol(tokenIndex)]
}
}
export class MarginAccount {
publicKey: PublicKey;
createTime: number; // used to determine when to update
// TODO maybe this is obviated by websocket feed onUpdate
accountFlags!: WideBits;
mangoGroup!: PublicKey;
owner!: PublicKey;
deposits!: number[];
borrows!: number[];
beingLiquidated!: boolean;
hasBorrows!: boolean;
info!: number[];
openOrders!: PublicKey[];
openOrdersAccounts: (OpenOrders | undefined)[] // undefined if an openOrdersAccount not yet initialized and has zeroKey
constructor(publicKey: PublicKey, decoded: any) {
this.publicKey = publicKey
this.createTime = getUnixTs()
this.openOrdersAccounts = new Array(NUM_MARKETS).fill(undefined)
Object.assign(this, decoded)
}
getNativeDeposit(mangoGroup: MangoGroup, tokenIndex: number): number { // insufficient precision
return mangoGroup.indexes[tokenIndex].deposit * this.deposits[tokenIndex]
}
getNativeBorrow(mangoGroup: MangoGroup, tokenIndex: number): number { // insufficient precision
return mangoGroup.indexes[tokenIndex].borrow * this.borrows[tokenIndex]
}
getUiDeposit(mangoGroup: MangoGroup, tokenIndex: number): number { // insufficient precision
return nativeToUi(this.getNativeDeposit(mangoGroup, tokenIndex), mangoGroup.mintDecimals[tokenIndex])
}
getUiBorrow(mangoGroup: MangoGroup, tokenIndex: number): number { // insufficient precision
return nativeToUi(this.getNativeBorrow(mangoGroup, tokenIndex), mangoGroup.mintDecimals[tokenIndex])
}
async loadOpenOrders(
connection: Connection,
dexProgramId: PublicKey
): Promise<(OpenOrders | undefined)[]> {
const promises: Promise<OpenOrders | undefined>[] = []
for (let i = 0; i < this.openOrders.length; i++) {
if (this.openOrders[i].equals(zeroKey)) {
promises.push(promiseUndef())
} else {
promises.push(OpenOrders.load(connection, this.openOrders[i], dexProgramId))
}
}
this.openOrdersAccounts = await Promise.all(promises)
return this.openOrdersAccounts
}
toPrettyString(
mangoGroup: MangoGroup,
prices: number[]
): string {
const lines = [
`MarginAccount: ${this.publicKey.toBase58()}`,
`Owner: ${this.owner.toBase58()}`,
`${"Token".padEnd(5)} ${"Assets".padEnd(10)} ${"Deposits".padEnd(10)} ${"Borrows".padEnd(10)}`,
]
for (let i = 0; i < mangoGroup.tokens.length; i++) {
const tokenSymbol = mangoGroup.getTokenSymbol(i)
const decimals = tokenToDecimals[tokenSymbol]
const assetStr = this.getAssets(mangoGroup)[i].toFixed(decimals).toString().padEnd(10)
const depositStr = this.getUiDeposit(mangoGroup, i).toFixed(decimals).toString().padEnd(10)
const borrowStr = this.getUiBorrow(mangoGroup, i).toFixed(decimals).toString().padEnd(10)
lines.push(
`${tokenSymbol.padEnd(5)} ${assetStr} ${depositStr} ${borrowStr}`
)
}
lines.push(`Coll. Ratio: ${this.getCollateralRatio(mangoGroup, prices).toFixed(4)}`)
lines.push(`Value: ${this.computeValue(mangoGroup, prices).toFixed(2)}`)
return lines.join('\n')
}
computeValue(
mangoGroup: MangoGroup,
prices: number[]
): number {
let value = 0
for (let i = 0; i < this.deposits.length; i++) {
value += (this.getUiDeposit(mangoGroup, i) - this.getUiBorrow(mangoGroup, i)) * prices[i]
}
for (let i = 0; i < this.openOrdersAccounts.length; i++) {
const oos = this.openOrdersAccounts[i]
if (oos != undefined) {
value += nativeToUi(oos.baseTokenTotal.toNumber(), mangoGroup.mintDecimals[i]) * prices[i]
value += nativeToUi(oos.quoteTokenTotal.toNumber() + oos['referrerRebatesAccrued'].toNumber(), mangoGroup.mintDecimals[NUM_TOKENS-1])
}
}
return value
}
async getValue(
connection: Connection,
mangoGroup: MangoGroup
): Promise<number> {
const prices = await mangoGroup.getPrices(connection)
return this.computeValue(mangoGroup, prices)
}
getDeposits(mangoGroup: MangoGroup): number[] {
const deposits = new Array<number>(NUM_TOKENS)
for (let i = 0; i < NUM_TOKENS; i++) {
deposits[i] = this.getUiDeposit(mangoGroup, i)
}
return deposits
}
getAssets(mangoGroup: MangoGroup): number[] {
const assets = new Array<number>(NUM_TOKENS)
for (let i = 0; i < NUM_TOKENS; i++) {
assets[i] = this.getUiDeposit(mangoGroup, i)
}
for (let i = 0; i < NUM_MARKETS; i++) {
const openOrdersAccount = this.openOrdersAccounts[i]
if (openOrdersAccount == undefined) {
continue
}
assets[i] += nativeToUi(openOrdersAccount.baseTokenTotal.toNumber(), mangoGroup.mintDecimals[i])
assets[NUM_TOKENS-1] += nativeToUi(openOrdersAccount.quoteTokenTotal.toNumber() + openOrdersAccount['referrerRebatesAccrued'].toNumber(), mangoGroup.mintDecimals[NUM_TOKENS-1])
}
return assets
}
getLiabs(mangoGroup: MangoGroup): number[] {
const liabs = new Array(NUM_TOKENS)
for (let i = 0; i < NUM_TOKENS; i++) {
liabs[i] = this.getUiBorrow(mangoGroup, i)
}
return liabs
}
getAssetsVal(mangoGroup: MangoGroup, prices: number[]): number {
let assetsVal = 0
for (let i = 0; i < NUM_TOKENS; i++) {
assetsVal += this.getUiDeposit(mangoGroup, i) * prices[i]
}
for (let i = 0; i < NUM_MARKETS; i++) {
const openOrdersAccount = this.openOrdersAccounts[i]
if (openOrdersAccount == undefined) {
continue
}
assetsVal += nativeToUi(openOrdersAccount.baseTokenTotal.toNumber(), mangoGroup.mintDecimals[i]) * prices[i]
assetsVal += nativeToUi(openOrdersAccount.quoteTokenTotal.toNumber() + openOrdersAccount['referrerRebatesAccrued'].toNumber(), mangoGroup.mintDecimals[NUM_TOKENS-1])
}
return assetsVal
}
getLiabsVal(mangoGroup: MangoGroup, prices: number[]): number {
let liabsVal = 0
for (let i = 0; i < NUM_TOKENS; i++) {
liabsVal += this.getUiBorrow(mangoGroup, i) * prices[i]
}
return liabsVal
}
getCollateralRatio(mangoGroup: MangoGroup, prices: number[]): number {
const assetsVal = this.getAssetsVal(mangoGroup, prices)
const liabsVal = this.getLiabsVal(mangoGroup, prices)
return assetsVal / liabsVal
}
async cancelAllOrdersByMarket(
connection: Connection,
client: MangoClient,
programId: PublicKey,
mangoGroup: MangoGroup,
market: Market,
bids: Orderbook,
asks: Orderbook,
owner: Account
): Promise<TransactionSignature[]> {
const marketIndex = mangoGroup.getMarketIndex(market)
const openOrdersAccount = this.openOrdersAccounts[marketIndex]
if (openOrdersAccount == undefined) { // no open orders for this market
return []
}
const orders = market.filterForOpenOrders(bids, asks, [openOrdersAccount])
return await Promise.all(orders.map(
(order) => (
client.cancelOrder(connection, programId, mangoGroup, this, owner, market, order)
)
))
}
}
export class MangoSrmAccount {
publicKey: PublicKey;
accountFlags!: WideBits;
mangoGroup!: PublicKey;
amount!: number;
constructor(publicKey: PublicKey, decoded: any) {
this.publicKey = publicKey
Object.assign(this, decoded)
}
getUiSrmAmount() {
return nativeToUi(this.amount, SRM_DECIMALS)
}
}
export class MangoClient {
confirmLevel: TransactionConfirmationStatus;
constructor(confirmLevel: TransactionConfirmationStatus = 'processed') {
this.confirmLevel = confirmLevel
}
async sendTransaction(
connection: Connection,
transaction: Transaction,
payer: Account,
additionalSigners: Account[],
timeout = 30000,
): Promise<TransactionSignature> {
transaction.recentBlockhash = (await connection.getRecentBlockhash('singleGossip')).blockhash
transaction.setSigners(payer.publicKey, ...additionalSigners.map( a => a.publicKey ))
const signers = [payer].concat(additionalSigners)
transaction.sign(...signers)
const rawTransaction = transaction.serialize()
const startTime = getUnixTs();
const txid: TransactionSignature = await connection.sendRawTransaction(
rawTransaction,
{
skipPreflight: true,
},
);
console.log('Started awaiting confirmation for', txid);
let done = false;
(async () => {
while (!done && (getUnixTs() - startTime) < timeout / 1000) {
connection.sendRawTransaction(rawTransaction, {
skipPreflight: true
});
await sleep(300);
}
})();
try {
await awaitTransactionSignatureConfirmation(txid, timeout, connection, this.confirmLevel);
} catch (err) {
if (err.timeout) {
throw new Error('Timed out awaiting confirmation on transaction');
}
let simulateResult: SimulatedTransactionResponse | null = null;
try {
simulateResult = (
await simulateTransaction(connection, transaction, 'singleGossip')
).value;
} catch (e) {
}
if (simulateResult && simulateResult.err) {
if (simulateResult.logs) {
for (let i = simulateResult.logs.length - 1; i >= 0; --i) {
const line = simulateResult.logs[i];
if (line.startsWith('Program log: ')) {
throw new Error(
'Transaction failed: ' + line.slice('Program log: '.length),
);
}
}
}
throw new Error(JSON.stringify(simulateResult.err));
}
throw new Error('Transaction failed');
} finally {
done = true;
}
console.log('Latency', txid, getUnixTs() - startTime);
return txid;
}
async sendTransactionDeprecated(
connection: Connection,
transaction: Transaction,
payer: Account,
additionalSigners: Account[],
): Promise<TransactionSignature> {
// TODO test on mainnet
transaction.recentBlockhash = (await connection.getRecentBlockhash('singleGossip')).blockhash
transaction.setSigners(payer.publicKey, ...additionalSigners.map( a => a.publicKey ))
const signers = [payer].concat(additionalSigners)
transaction.sign(...signers)
const rawTransaction = transaction.serialize()
return await sendAndConfirmRawTransaction(connection, rawTransaction, {skipPreflight: true})
}
async initMangoGroup(
connection: Connection,
programId: PublicKey,
payer: PublicKey,
) {
throw new Error("Not Implemented");
}
async initMarginAccount(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
owner: Account, // assumed to be same as payer for now
): Promise<PublicKey> {
// Create a Solana account for the MarginAccount and allocate space
const accInstr = await createAccountInstruction(connection,
owner.publicKey, MarginAccountLayout.span, programId)
// Specify the accounts this instruction takes in (see program/src/instruction.rs)
const keys = [
{ isSigner: false, isWritable: false, pubkey: mangoGroup.publicKey },
{ isSigner: false, isWritable: true, pubkey: accInstr.account.publicKey },
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY }
]
// Encode and create instruction for actual initMarginAccount instruction
const data = encodeMangoInstruction({ InitMarginAccount: {} })
const initMarginAccountInstruction = new TransactionInstruction( { keys, data, programId })
// Add all instructions to one atomic transaction
const transaction = new Transaction()
transaction.add(accInstr.instruction)
transaction.add(initMarginAccountInstruction)
// Specify signers in addition to the wallet
const additionalSigners = [
accInstr.account,
]
// sign, send and confirm transaction
await this.sendTransaction(connection, transaction, owner, additionalSigners)
return accInstr.account.publicKey
}
async deposit(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
owner: Account,
token: PublicKey,
tokenAcc: PublicKey,
quantity: number
): Promise<TransactionSignature> {
const tokenIndex = mangoGroup.getTokenIndex(token)
const nativeQuantity = uiToNative(quantity, mangoGroup.mintDecimals[tokenIndex])
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey},
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: true, pubkey: tokenAcc },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[tokenIndex] },
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }
]
const data = encodeMangoInstruction({Deposit: {quantity: nativeQuantity}})
const instruction = new TransactionInstruction( { keys, data, programId })
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
async withdraw(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
owner: Account,
token: PublicKey,
tokenAcc: PublicKey,
quantity: number
): Promise<TransactionSignature> {
const tokenIndex = mangoGroup.getTokenIndex(token)
const nativeQuantity = uiToNative(quantity, mangoGroup.mintDecimals[tokenIndex])
const instruction = makeWithdrawInstruction(
programId,
mangoGroup.publicKey,
marginAccount.publicKey,
owner.publicKey,
mangoGroup.signerKey,
tokenAcc,
mangoGroup.vaults[tokenIndex],
marginAccount.openOrders,
mangoGroup.oracles,
nativeQuantity,
);
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
async borrow(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
owner: Account,
token: PublicKey,
quantity: number
): Promise<TransactionSignature> {
const tokenIndex = mangoGroup.getTokenIndex(token)
const nativeQuantity = uiToNative(quantity, mangoGroup.mintDecimals[tokenIndex])
const instruction = makeBorrowInstruction(
programId,
mangoGroup.publicKey,
marginAccount.publicKey,
owner.publicKey,
tokenIndex,
marginAccount.openOrders,
mangoGroup.oracles,
nativeQuantity,
);
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
async borrowAndWithdraw(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
wallet: Wallet | Account,
token: PublicKey,
tokenAcc: PublicKey,
withdrawQuantity: number
): Promise<TransactionSignature> {
const transaction = new Transaction()
const tokenIndex = mangoGroup.getTokenIndex(token)
const tokenBalance = marginAccount.getUiDeposit(mangoGroup, tokenIndex)
const borrowQuantity = withdrawQuantity - tokenBalance
const nativeBorrowQuantity = uiToNative(
borrowQuantity,
mangoGroup.mintDecimals[tokenIndex]
)
const borrowInstruction = makeBorrowInstruction(
programId,
mangoGroup.publicKey,
marginAccount.publicKey,
wallet.publicKey,
tokenIndex,
marginAccount.openOrders,
mangoGroup.oracles,
nativeBorrowQuantity,
);
transaction.add(borrowInstruction);
// uiToNative() uses Math.round resulting in rounding
// errors, so we use Math.floor here instead
const nativeWithdrawQuantity = new BN(
Math.floor(
withdrawQuantity * Math.pow(10, mangoGroup.mintDecimals[tokenIndex]),
) * 0.98,
);
const withdrawInstruction = makeWithdrawInstruction(
programId,
mangoGroup.publicKey,
marginAccount.publicKey,
wallet.publicKey,
mangoGroup.signerKey,
tokenAcc,
mangoGroup.vaults[tokenIndex],
marginAccount.openOrders,
mangoGroup.oracles,
nativeWithdrawQuantity,
);
transaction.add(withdrawInstruction);
const signers = []
return await this.sendTransaction(connection, transaction, wallet, signers)
}
async settleBorrow(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
owner: Account,
token: PublicKey,
quantity: number
): Promise<TransactionSignature> {
const tokenIndex = mangoGroup.getTokenIndex(token)
const nativeQuantity = uiToNative(quantity, mangoGroup.mintDecimals[tokenIndex])
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey},
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }
]
const data = encodeMangoInstruction({SettleBorrow: {tokenIndex: new BN(tokenIndex), quantity: nativeQuantity}})
const instruction = new TransactionInstruction( { keys, data, programId })
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
/**
* Call SettleFunds on each market, then call SettleBorrow for each token in one transaction
* @param connection
* @param programId
* @param mangoGroup
* @param marginAccount
* @param markets
* @param owner
*/
async settleAll(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
markets: Market[],
owner: Account
): Promise<TransactionSignature | null> {
const transaction = new Transaction()
const assetGains: number[] = new Array(NUM_TOKENS).fill(0)
for (let i = 0; i < NUM_MARKETS; i++) {
const openOrdersAccount = marginAccount.openOrdersAccounts[i]
if (openOrdersAccount === undefined) {
continue
} else if (openOrdersAccount.quoteTokenFree.toNumber() + openOrdersAccount['referrerRebatesAccrued'].toNumber() === 0 && openOrdersAccount.baseTokenFree.toNumber() === 0) {
continue
}
assetGains[i] += openOrdersAccount.baseTokenFree.toNumber()
assetGains[NUM_TOKENS-1] += openOrdersAccount.quoteTokenFree.toNumber() + openOrdersAccount['referrerRebatesAccrued'].toNumber()
const spotMarket = markets[i]
const dexSigner = await PublicKey.createProgramAddress(
[
spotMarket.publicKey.toBuffer(),
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8)
],
spotMarket.programId
)
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey},
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
{ isSigner: false, isWritable: false, pubkey: spotMarket.programId },
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
{ isSigner: false, isWritable: true, pubkey: marginAccount.openOrders[i] },
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].baseVault },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].quoteVault },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[i] },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[mangoGroup.vaults.length - 1] },
{ isSigner: false, isWritable: false, pubkey: dexSigner },
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
]
const data = encodeMangoInstruction( {SettleFunds: {}} )
const instruction = new TransactionInstruction( { keys, data, programId })
transaction.add(instruction)
}
const deposits = marginAccount.getDeposits(mangoGroup)
const liabs = marginAccount.getLiabs(mangoGroup)
for (let i = 0; i < NUM_TOKENS; i++) { // TODO test this. maybe it hits transaction size limit
const deposit = deposits[i] + assetGains[i]
if (deposit === 0 || liabs[i] === 0) {
continue
}
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey},
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }
]
const data = encodeMangoInstruction({SettleBorrow: {tokenIndex: new BN(i), quantity: uiToNative(liabs[i] * 2, mangoGroup.mintDecimals[i])}})
const instruction = new TransactionInstruction( { keys, data, programId })
transaction.add(instruction)
}
const additionalSigners = []
if (transaction.instructions.length == 0) {
return null
} else {
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
}
async liquidate(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
liqeeMarginAccount: MarginAccount, // liquidatee marginAccount
liqor: Account, // liquidator
tokenAccs: PublicKey[],
depositQuantities: number[]
): Promise<TransactionSignature> {
const depositsBN: BN[] = []
for (let i = 0; i < mangoGroup.tokens.length; i++) {
depositsBN[i] = uiToNative(depositQuantities[i], mangoGroup.mintDecimals[i])
}
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey},
{ isSigner: true, isWritable: false, pubkey: liqor.publicKey },
{ isSigner: false, isWritable: true, pubkey: liqeeMarginAccount.publicKey },
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
...liqeeMarginAccount.openOrders.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })),
...mangoGroup.oracles.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })),
...mangoGroup.vaults.map( (pubkey) => ( { isSigner: false, isWritable: true, pubkey })),
...tokenAccs.map( (pubkey) => ( { isSigner: false, isWritable: true, pubkey })),
]
const data = encodeMangoInstruction({Liquidate: {depositQuantities: depositsBN}})
const instruction = new TransactionInstruction( { keys, data, programId })
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, liqor, additionalSigners)
}
async forceCancelOrders(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
liqeeMarginAccount: MarginAccount,
liqor: Account,
spotMarket: Market,
limit: number
): Promise<TransactionSignature> {
const limitBn = new BN(limit)
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
const dexSigner = await PublicKey.createProgramAddress(
[
spotMarket.publicKey.toBuffer(),
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8)
],
spotMarket.programId
)
const instruction = makeForceCancelOrdersInstruction(
programId,
mangoGroup.publicKey,
liqor.publicKey,
liqeeMarginAccount.publicKey,
mangoGroup.vaults[marketIndex],
mangoGroup.vaults[NUM_TOKENS-1],
spotMarket.publicKey,
spotMarket.bidsAddress,
spotMarket.asksAddress,
mangoGroup.signerKey,
spotMarket['_decoded'].eventQueue,
spotMarket['_decoded'].baseVault,
spotMarket['_decoded'].quoteVault,
dexSigner,
mangoGroup.dexProgramId,
liqeeMarginAccount.openOrders,
mangoGroup.oracles,
limitBn
)
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, liqor, additionalSigners)
}
async partialLiquidate(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
liqeeMarginAccount: MarginAccount,
liqor: Account,
liqorInTokenWallet: PublicKey,
liqorOutTokenWallet: PublicKey,
inTokenIndex: number,
outTokenIndex: number,
maxDeposit: number
): Promise<TransactionSignature> {
const maxDepositBn: BN = uiToNative(maxDeposit, mangoGroup.mintDecimals[inTokenIndex])
const instruction = makePartialLiquidateInstruction(
programId,
mangoGroup.publicKey,
liqor.publicKey,
liqorInTokenWallet,
liqorOutTokenWallet,
liqeeMarginAccount.publicKey,
mangoGroup.vaults[inTokenIndex],
mangoGroup.vaults[outTokenIndex],
mangoGroup.signerKey,
liqeeMarginAccount.openOrders,
mangoGroup.oracles,
maxDepositBn
)
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, liqor, additionalSigners)
}
async depositSrm(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
owner: Account,
srmAccount: PublicKey,
quantity: number,
mangoSrmAccount?: PublicKey
): Promise<PublicKey> {
const transaction = new Transaction()
const additionalSigners: Account[] = []
if (!mangoSrmAccount) {
const accInstr = await createAccountInstruction(connection,
owner.publicKey, MangoSrmAccountLayout.span, programId)
transaction.add(accInstr.instruction)
additionalSigners.push(accInstr.account)
mangoSrmAccount = accInstr.account.publicKey
}
const nativeQuantity = uiToNative(quantity, SRM_DECIMALS)
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
{ isSigner: false, isWritable: true, pubkey: mangoSrmAccount },
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: true, pubkey: srmAccount },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.srmVault },
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY }
]
const data = encodeMangoInstruction({DepositSrm: {quantity: nativeQuantity}})
const instruction = new TransactionInstruction( { keys, data, programId })
transaction.add(instruction)
await this.sendTransaction(connection, transaction, owner, additionalSigners)
return mangoSrmAccount
}
async withdrawSrm(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
mangoSrmAccount: MangoSrmAccount,
owner: Account,
srmAccount: PublicKey,
quantity: number
): Promise<TransactionSignature> {
const nativeQuantity = uiToNative(quantity, SRM_DECIMALS)
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
{ isSigner: false, isWritable: true, pubkey: mangoSrmAccount.publicKey },
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: true, pubkey: srmAccount },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.srmVault },
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }
]
const data = encodeMangoInstruction({WithdrawSrm: {quantity: nativeQuantity}})
const instruction = new TransactionInstruction( { keys, data, programId })
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
async placeOrder(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
spotMarket: Market,
owner: Account,
side: 'buy' | 'sell',
price: number,
size: number,
orderType?: 'limit' | 'ioc' | 'postOnly',
clientId?: BN,
timeout?: number
): Promise<TransactionSignature> {
// TODO allow wrapped SOL wallets
orderType = orderType || 'limit'
const limitPrice = spotMarket.priceNumberToLots(price)
const maxBaseQuantity = spotMarket.baseSizeNumberToLots(size)
const feeTier = getFeeTier(0, nativeToUi(mangoGroup.nativeSrm || 0, SRM_DECIMALS));
const rates = getFeeRates(feeTier);
const maxQuoteQuantity = new BN(
spotMarket['_decoded'].quoteLotSize.toNumber() * (1 + rates.taker),
).mul(spotMarket.baseSizeNumberToLots(size).mul(spotMarket.priceNumberToLots(price)));
if (maxBaseQuantity.lte(new BN(0))) {
throw new Error('size too small')
}
if (limitPrice.lte(new BN(0))) {
throw new Error('invalid price')
}
const selfTradeBehavior = 'decrementTake'
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
const vaultIndex = (side === 'buy') ? mangoGroup.vaults.length - 1 : marketIndex
// Add all instructions to one atomic transaction
const transaction = new Transaction()
// Specify signers in addition to the wallet
const additionalSigners: Account[] = []
// Create a Solana account for the open orders account if it's missing
const openOrdersKeys: PublicKey[] = [];
for (let i = 0; i < marginAccount.openOrders.length; i++) {
if (i === marketIndex && marginAccount.openOrders[marketIndex].equals(zeroKey)) {
// open orders missing for this market; create a new one now
const openOrdersSpace = OpenOrders.getLayout(mangoGroup.dexProgramId).span
const openOrdersLamports = await connection.getMinimumBalanceForRentExemption(openOrdersSpace, 'singleGossip')
const accInstr = await createAccountInstruction(
connection, owner.publicKey, openOrdersSpace, mangoGroup.dexProgramId, openOrdersLamports
)
transaction.add(accInstr.instruction)
additionalSigners.push(accInstr.account)
openOrdersKeys.push(accInstr.account.publicKey)
} else {
openOrdersKeys.push(marginAccount.openOrders[i])
}
}
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey},
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
{ isSigner: false, isWritable: false, pubkey: spotMarket.programId },
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].requestQueue },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].eventQueue },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].bids },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].asks },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[vaultIndex] },
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].baseVault },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].quoteVault },
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.srmVault },
...openOrdersKeys.map( (pubkey) => ( { isSigner: false, isWritable: true, pubkey })),
...mangoGroup.oracles.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })),
]
const data = encodeMangoInstruction(
{
PlaceOrder:
clientId
? { side, limitPrice, maxBaseQuantity, maxQuoteQuantity, selfTradeBehavior, orderType, clientId, limit: 65535}
: { side, limitPrice, maxBaseQuantity, maxQuoteQuantity, selfTradeBehavior, orderType, limit: 65535}
}
)
const placeOrderInstruction = new TransactionInstruction( { keys, data, programId })
transaction.add(placeOrderInstruction)
// sign, send and confirm transaction
if (timeout) {
return await this.sendTransaction(connection, transaction, owner, additionalSigners, timeout)
} else {
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
}
async placeAndSettle(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
spotMarket: Market,
owner: Account,
side: 'buy' | 'sell',
price: number,
size: number,
orderType?: 'limit' | 'ioc' | 'postOnly',
clientId?: BN,
timeout?: number
): Promise<TransactionSignature> {
// TODO allow wrapped SOL wallets
orderType = orderType || 'limit'
const limitPrice = spotMarket.priceNumberToLots(price)
const maxBaseQuantity = spotMarket.baseSizeNumberToLots(size)
const feeTier = getFeeTier(0, nativeToUi(mangoGroup.nativeSrm || 0, SRM_DECIMALS));
const rates = getFeeRates(feeTier);
const maxQuoteQuantity = new BN(
spotMarket['_decoded'].quoteLotSize.toNumber() * (1 + rates.taker),
).mul(spotMarket.baseSizeNumberToLots(size).mul(spotMarket.priceNumberToLots(price)));
if (maxBaseQuantity.lte(new BN(0))) {
throw new Error('size too small')
}
if (limitPrice.lte(new BN(0))) {
throw new Error('invalid price')
}
const selfTradeBehavior = 'decrementTake'
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
const vaultIndex = (side === 'buy') ? mangoGroup.vaults.length - 1 : marketIndex
// Add all instructions to one atomic transaction
const transaction = new Transaction()
// Specify signers in addition to the wallet
const additionalSigners: Account[] = []
// Create a Solana account for the open orders account if it's missing
const openOrdersKeys: PublicKey[] = [];
for (let i = 0; i < marginAccount.openOrders.length; i++) {
if (i === marketIndex && marginAccount.openOrders[marketIndex].equals(zeroKey)) {
// open orders missing for this market; create a new one now
const openOrdersSpace = OpenOrders.getLayout(mangoGroup.dexProgramId).span
const openOrdersLamports = await connection.getMinimumBalanceForRentExemption(openOrdersSpace, 'singleGossip')
const accInstr = await createAccountInstruction(
connection, owner.publicKey, openOrdersSpace, mangoGroup.dexProgramId, openOrdersLamports
)
transaction.add(accInstr.instruction)
additionalSigners.push(accInstr.account)
openOrdersKeys.push(accInstr.account.publicKey)
} else {
openOrdersKeys.push(marginAccount.openOrders[i])
}
}
const dexSigner = await PublicKey.createProgramAddress(
[
spotMarket.publicKey.toBuffer(),
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
],
spotMarket.programId,
);
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey},
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
{ isSigner: false, isWritable: false, pubkey: spotMarket.programId },
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].requestQueue },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].eventQueue },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].bids },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].asks },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[marketIndex] },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[NUM_TOKENS-1] },
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].baseVault },
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].quoteVault },
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY },
{ isSigner: false, isWritable: true, pubkey: mangoGroup.srmVault },
{ isSigner: false, isWritable: false, pubkey: dexSigner },
...openOrdersKeys.map( (pubkey) => ( { isSigner: false, isWritable: true, pubkey })),
...mangoGroup.oracles.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })),
]
const data = encodeMangoInstruction(
{
PlaceAndSettle:
clientId
? { side, limitPrice, maxBaseQuantity, maxQuoteQuantity, selfTradeBehavior, orderType, clientId, limit: 65535}
: { side, limitPrice, maxBaseQuantity, maxQuoteQuantity, selfTradeBehavior, orderType, limit: 65535}
}
)
const placeOrderInstruction = new TransactionInstruction( { keys, data, programId })
transaction.add(placeOrderInstruction)
// sign, send and confirm transaction
if (timeout) {
return await this.sendTransaction(connection, transaction, owner, additionalSigners, timeout)
} else {
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
}
async settleFunds(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
owner: Account,
spotMarket: Market,
): Promise<TransactionSignature> {
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
const dexSigner = await PublicKey.createProgramAddress(
[
spotMarket.publicKey.toBuffer(),
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8)
],
spotMarket.programId
)
const instruction = makeSettleFundsInstruction(
programId,
mangoGroup.publicKey,
owner.publicKey,
marginAccount.publicKey,
spotMarket.programId,
spotMarket.publicKey,
marginAccount.openOrders[marketIndex],
mangoGroup.signerKey,
spotMarket['_decoded'].baseVault,
spotMarket['_decoded'].quoteVault,
mangoGroup.vaults[marketIndex],
mangoGroup.vaults[mangoGroup.vaults.length - 1],
dexSigner
)
const transaction = new Transaction()
transaction.add(instruction)
// Specify signers in addition to the owner account
const additionalSigners = []
// sign, send and confirm transaction
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
async cancelOrder(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
owner: Account,
spotMarket: Market,
order: Order,
): Promise<TransactionSignature> {
const instruction = makeCancelOrderInstruction(
programId,
mangoGroup.publicKey,
owner.publicKey,
marginAccount.publicKey,
spotMarket.programId,
spotMarket.publicKey,
spotMarket['_decoded'].bids,
spotMarket['_decoded'].asks,
order.openOrdersAddress,
mangoGroup.signerKey,
spotMarket['_decoded'].eventQueue,
order
)
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
async addMarginAccountInfo(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
owner: Account,
info: string
): Promise<TransactionSignature> {
const instruction = makeAddMarginAccountInfoInstruction(
programId,
mangoGroup.publicKey,
owner.publicKey,
marginAccount.publicKey,
info
)
const transaction = new Transaction()
transaction.add(instruction)
const additionalSigners = []
return await this.sendTransaction(connection, transaction, owner, additionalSigners)
}
async getMangoGroup(
connection: Connection,
mangoGroupPk: PublicKey,
srmVaultPk?: PublicKey
): Promise<MangoGroup> {
if (srmVaultPk) {
const [acc, srmVaultAcc] = await Promise.all(
[connection.getAccountInfo(mangoGroupPk), connection.getAccountInfo(srmVaultPk)]
)
const decoded = MangoGroupLayout.decode(acc == null ? undefined : acc.data);
if (!srmVaultAcc) { return new MangoGroup(mangoGroupPk, decoded) }
const srmVault = parseTokenAccountData(srmVaultAcc.data)
return new MangoGroup(mangoGroupPk, decoded, srmVault.amount)
} else {
const acc = await connection.getAccountInfo(mangoGroupPk);
const decoded = MangoGroupLayout.decode(acc == null ? undefined : acc.data);
return new MangoGroup(mangoGroupPk, decoded);
}
}
async getMarginAccount(
connection: Connection,
marginAccountPk: PublicKey,
dexProgramId: PublicKey
): Promise<MarginAccount> {
const acc = await connection.getAccountInfo(marginAccountPk, 'singleGossip')
const ma = new MarginAccount(marginAccountPk, MarginAccountLayout.decode(acc == null ? undefined : acc.data))
await ma.loadOpenOrders(connection, dexProgramId)
return ma
}
async getAllMarginAccounts(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
filters?: [any]
): Promise<MarginAccount[]> {
const marginAccountsFilters = [
{
memcmp: {
offset: MarginAccountLayout.offsetOf('mangoGroup'),
bytes: mangoGroup.publicKey.toBase58(),
}
},
{
dataSize: MarginAccountLayout.span,
},
];
if(filters && filters.length) {
marginAccountsFilters.push(...filters);
}
const marginAccounts = await getFilteredProgramAccounts(connection, programId, marginAccountsFilters)
.then((accounts) => (
accounts.map(({ publicKey, accountInfo }) =>
new MarginAccount(publicKey, MarginAccountLayout.decode(accountInfo == null ? undefined : accountInfo.data))
)
))
const openOrderPks = marginAccounts
.map((ma) => ma.openOrders.filter((pk) => !pk.equals(zeroKey)))
.flat();
const openOrderAccountInfos = await getMultipleAccounts(
connection,
openOrderPks,
);
const openOrders = openOrderAccountInfos.map(
({ publicKey, accountInfo }) =>
OpenOrders.fromAccountInfo(
publicKey,
accountInfo,
mangoGroup.dexProgramId,
),
);
const pkToOpenOrdersAccount = {}
openOrders.forEach(
(openOrdersAccount) => (
pkToOpenOrdersAccount[openOrdersAccount.publicKey.toBase58()] = openOrdersAccount
)
)
for (const ma of marginAccounts) {
for (let i = 0; i < ma.openOrders.length; i++) {
if (ma.openOrders[i].toBase58() in pkToOpenOrdersAccount) {
ma.openOrdersAccounts[i] = pkToOpenOrdersAccount[ma.openOrders[i].toBase58()]
}
}
}
return marginAccounts
}
async getAllMarginAccountsWithBorrows(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup
): Promise<MarginAccount[]> {
return await this.getAllMarginAccounts(connection, programId, mangoGroup, [{
memcmp: {
offset: MarginAccountLayout.offsetOf('hasBorrows'),
bytes: "2" // [1] base58 encoded
}
}])
}
async getMarginAccountsForOwner(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
owner: Account | Wallet
): Promise<MarginAccount[]> {
const filters = [
{
memcmp: {
offset: MarginAccountLayout.offsetOf('mangoGroup'),
bytes: mangoGroup.publicKey.toBase58(),
},
},
{
memcmp: {
offset: MarginAccountLayout.offsetOf('owner'),
bytes: owner.publicKey.toBase58(),
}
},
{
dataSize: MarginAccountLayout.span,
},
];
const accounts = await getFilteredProgramAccounts(connection, programId, filters);
const marginAccounts = accounts.map(
({ publicKey, accountInfo }) =>
new MarginAccount(publicKey, MarginAccountLayout.decode(accountInfo == null ? undefined : accountInfo.data))
)
await Promise.all(marginAccounts.map((ma) => ma.loadOpenOrders(connection, mangoGroup.dexProgramId)))
return marginAccounts
}
async getMangoSrmAccount(
connection: Connection,
mangoSrmAccountPk: PublicKey
): Promise<MangoSrmAccount> {
const acc = await connection.getAccountInfo(mangoSrmAccountPk, 'singleGossip')
return new MangoSrmAccount(mangoSrmAccountPk, MangoSrmAccountLayout.decode(acc == null ? undefined : acc.data))
}
async getMangoSrmAccountsForOwner(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
owner: Account | Wallet
): Promise<MangoSrmAccount[]> {
const filters = [
{
memcmp: {
offset: MangoSrmAccountLayout.offsetOf('mangoGroup'),
bytes: mangoGroup.publicKey.toBase58(),
},
},
{
memcmp: {
offset: MangoSrmAccountLayout.offsetOf('owner'),
bytes: owner.publicKey.toBase58(),
}
},
{
dataSize: MangoSrmAccountLayout.span,
},
];
const accounts = await getFilteredProgramAccounts(connection, programId, filters);
const srmAccounts = accounts.map(
({ publicKey, accountInfo }) =>
new MangoSrmAccount(publicKey, MangoSrmAccountLayout.decode(accountInfo == null ? undefined : accountInfo.data))
)
return srmAccounts
}
}