2022-05-11 04:33:01 -07:00
import { BN } from '@project-serum/anchor' ;
2022-04-12 07:19:58 -07:00
import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes' ;
2022-08-31 02:36:44 -07:00
import { Order , Orderbook } from '@project-serum/serum/lib/market' ;
2022-04-08 08:21:49 -07:00
import { PublicKey } from '@solana/web3.js' ;
2022-04-12 21:37:36 -07:00
import { MangoClient } from '../client' ;
2022-08-23 04:47:08 -07:00
import { nativeI80F48ToUi , toNative , toUiDecimals } from '../utils' ;
import { Bank } from './bank' ;
2022-06-03 06:34:05 -07:00
import { Group } from './group' ;
2022-07-13 10:18:55 -07:00
import { HealthCache , HealthCacheDto } from './healthCache' ;
2022-08-23 04:47:08 -07:00
import { I80F48 , I80F48Dto , ONE_I80F48 , ZERO_I80F48 } from './I80F48' ;
2022-09-20 03:57:01 -07:00
import { PerpOrder } from './perp' ;
2022-08-31 02:36:44 -07:00
import { Serum3Market , Serum3Side } from './serum3' ;
2022-04-02 23:57:45 -07:00
export class MangoAccount {
2022-06-22 02:21:02 -07:00
public tokens : TokenPosition [ ] ;
public serum3 : Serum3Orders [ ] ;
2022-08-18 04:45:31 -07:00
public perps : PerpPosition [ ] ;
2022-09-20 03:57:01 -07:00
public perpOpenOrders : PerpOo [ ] ;
2022-04-12 07:19:58 -07:00
public name : string ;
2022-04-02 23:57:45 -07:00
static from (
publicKey : PublicKey ,
obj : {
group : PublicKey ;
owner : PublicKey ;
2022-08-04 01:41:54 -07:00
name : number [ ] ;
2022-04-02 23:57:45 -07:00
delegate : PublicKey ;
beingLiquidated : number ;
accountNum : number ;
bump : number ;
2022-08-15 02:10:33 -07:00
netDeposits : BN ;
netSettled : BN ;
2022-08-04 00:07:32 -07:00
headerVersion : number ;
2022-08-04 01:41:54 -07:00
tokens : unknown ;
2022-08-04 10:42:41 -07:00
serum3 : unknown ;
2022-08-04 01:41:54 -07:00
perps : unknown ;
perpOpenOrders : unknown ;
2022-04-02 23:57:45 -07:00
} ,
) {
return new MangoAccount (
publicKey ,
obj . group ,
obj . owner ,
2022-08-04 01:41:54 -07:00
obj . name ,
2022-04-02 23:57:45 -07:00
obj . delegate ,
obj . beingLiquidated ,
obj . accountNum ,
obj . bump ,
2022-08-04 01:41:54 -07:00
obj . netDeposits ,
obj . netSettled ,
obj . headerVersion ,
obj . tokens as TokenPositionDto [ ] ,
obj . serum3 as Serum3PositionDto [ ] ,
obj . perps as PerpPositionDto [ ] ,
2022-09-20 03:57:01 -07:00
obj . perpOpenOrders as PerpOoDto [ ] ,
2022-08-04 10:42:41 -07:00
{ } as any ,
2022-04-02 23:57:45 -07:00
) ;
}
constructor (
public publicKey : PublicKey ,
2022-04-07 09:58:42 -07:00
public group : PublicKey ,
public owner : PublicKey ,
2022-08-04 01:41:54 -07:00
name : number [ ] ,
2022-04-07 09:58:42 -07:00
public delegate : PublicKey ,
2022-08-04 01:41:54 -07:00
beingLiquidated : number ,
public accountNum : number ,
bump : number ,
2022-08-15 02:10:33 -07:00
netDeposits : BN ,
netSettled : BN ,
2022-08-04 01:41:54 -07:00
headerVersion : number ,
2022-07-25 07:07:53 -07:00
tokens : TokenPositionDto [ ] ,
serum3 : Serum3PositionDto [ ] ,
perps : PerpPositionDto [ ] ,
2022-09-20 03:57:01 -07:00
perpOpenOrders : PerpOoDto [ ] ,
2022-08-31 02:41:12 -07:00
public accountData : undefined | MangoAccountData ,
2022-04-02 23:57:45 -07:00
) {
2022-04-12 07:19:58 -07:00
this . name = utf8 . decode ( new Uint8Array ( name ) ) . split ( '\x00' ) [ 0 ] ;
2022-07-25 07:07:53 -07:00
this . tokens = tokens . map ( ( dto ) = > TokenPosition . from ( dto ) ) ;
this . serum3 = serum3 . map ( ( dto ) = > Serum3Orders . from ( dto ) ) ;
2022-08-18 04:45:31 -07:00
this . perps = perps . map ( ( dto ) = > PerpPosition . from ( dto ) ) ;
2022-09-20 03:57:01 -07:00
this . perpOpenOrders = perpOpenOrders . map ( ( dto ) = > PerpOo . from ( dto ) ) ;
2022-08-31 02:41:12 -07:00
this . accountData = undefined ;
2022-04-02 23:57:45 -07:00
}
2022-08-27 00:55:55 -07:00
async reload ( client : MangoClient , group : Group ) : Promise < MangoAccount > {
2022-09-02 15:47:09 -07:00
const mangoAccount = await client . getMangoAccount ( this ) ;
await mangoAccount . reloadAccountData ( client , group ) ;
Object . assign ( this , mangoAccount ) ;
return mangoAccount ;
2022-07-04 03:29:35 -07:00
}
2022-09-05 09:31:57 -07:00
async reloadWithSlot (
client : MangoClient ,
group : Group ,
) : Promise < { value : MangoAccount ; slot : number } > {
const resp = await client . getMangoAccountWithSlot ( this . publicKey ) ;
await resp ? . value . reloadAccountData ( client , group ) ;
Object . assign ( this , resp ? . value ) ;
return { value : resp ! . value , slot : resp ! . slot } ;
}
2022-08-27 00:55:55 -07:00
async reloadAccountData (
client : MangoClient ,
group : Group ,
) : Promise < MangoAccount > {
2022-07-04 03:29:35 -07:00
this . accountData = await client . computeAccountData ( group , this ) ;
2022-08-27 00:55:55 -07:00
return this ;
2022-04-12 21:37:36 -07:00
}
2022-08-31 02:36:44 -07:00
tokensActive ( ) : TokenPosition [ ] {
return this . tokens . filter ( ( token ) = > token . isActive ( ) ) ;
}
serum3Active ( ) : Serum3Orders [ ] {
return this . serum3 . filter ( ( serum3 ) = > serum3 . isActive ( ) ) ;
}
perpActive ( ) : PerpPosition [ ] {
return this . perps . filter ( ( perp ) = > perp . isActive ( ) ) ;
}
2022-09-20 03:57:01 -07:00
perpOrdersActive ( ) : PerpOo [ ] {
return this . perpOpenOrders . filter (
( oo ) = > oo . orderMarket !== PerpOo . OrderMarketUnset ,
) ;
}
2022-06-22 02:21:02 -07:00
findToken ( tokenIndex : number ) : TokenPosition | undefined {
2022-04-02 23:57:45 -07:00
return this . tokens . find ( ( ta ) = > ta . tokenIndex == tokenIndex ) ;
}
2022-06-22 02:21:02 -07:00
findSerum3Account ( marketIndex : number ) : Serum3Orders | undefined {
2022-04-08 03:30:21 -07:00
return this . serum3 . find ( ( sa ) = > sa . marketIndex == marketIndex ) ;
}
2022-08-18 07:19:37 -07:00
// How to navigate
// * if a function is returning a I80F48, then usually the return value is in native quote or native token, unless specified
2022-08-18 07:39:22 -07:00
// * if a function is returning a number, then usually the return value is in ui tokens, unless specified
2022-08-18 07:19:37 -07:00
// * functions try to be explicit by having native or ui in the name to better reflect the value
// * some values might appear unexpected large or small, usually the doc contains a "note"
2022-04-02 23:57:45 -07:00
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native balance for a token , is signed
2022-08-18 07:19:37 -07:00
* /
getTokenBalance ( bank : Bank ) : I80F48 {
2022-08-23 04:47:08 -07:00
const tp = this . findToken ( bank . tokenIndex ) ;
2022-09-01 00:48:23 -07:00
return tp ? tp . balance ( bank ) : ZERO_I80F48 ( ) ;
2022-08-18 07:19:37 -07:00
}
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native deposits for a token , 0 if position has borrows
2022-08-18 07:19:37 -07:00
* /
getTokenDeposits ( bank : Bank ) : I80F48 {
2022-08-23 04:47:08 -07:00
const tp = this . findToken ( bank . tokenIndex ) ;
2022-09-01 00:48:23 -07:00
return tp ? tp . deposits ( bank ) : ZERO_I80F48 ( ) ;
2022-07-04 03:29:35 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native borrows for a token , 0 if position has deposits
2022-08-18 07:19:37 -07:00
* /
getTokenBorrows ( bank : Bank ) : I80F48 {
2022-08-23 04:47:08 -07:00
const tp = this . findToken ( bank . tokenIndex ) ;
2022-09-01 00:48:23 -07:00
return tp ? tp . borrows ( bank ) : ZERO_I80F48 ( ) ;
2022-07-04 03:29:35 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns UI balance for a token , is signed
2022-08-18 07:19:37 -07:00
* /
getTokenBalanceUi ( bank : Bank ) : number {
2022-08-23 04:47:08 -07:00
const tp = this . findToken ( bank . tokenIndex ) ;
return tp ? tp . balanceUi ( bank ) : 0 ;
2022-04-02 23:57:45 -07:00
}
2022-05-11 04:33:01 -07:00
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns UI deposits for a token , 0 or more
2022-08-18 07:19:37 -07:00
* /
getTokenDepositsUi ( bank : Bank ) : number {
2022-06-29 12:55:39 -07:00
const ta = this . findToken ( bank . tokenIndex ) ;
2022-08-18 07:19:37 -07:00
return ta ? ta . depositsUi ( bank ) : 0 ;
2022-06-29 12:55:39 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns UI borrows for a token , 0 or less
2022-08-18 07:19:37 -07:00
* /
getTokenBorrowsUi ( bank : Bank ) : number {
2022-06-29 12:55:39 -07:00
const ta = this . findToken ( bank . tokenIndex ) ;
2022-08-18 07:19:37 -07:00
return ta ? ta . borrowsUi ( bank ) : 0 ;
2022-06-29 12:55:39 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
* Health , see health . rs or https : //docs.mango.markets/mango-markets/health-overview
* @param healthType
* @returns raw health number , in native quote
* /
2022-08-31 02:41:12 -07:00
getHealth ( healthType : HealthType ) : I80F48 | undefined {
2022-07-12 03:05:19 -07:00
return healthType == HealthType . init
2022-08-31 02:41:12 -07:00
? this . accountData ? . initHealth
: this . accountData ? . maintHealth ;
2022-07-12 03:05:19 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
* Health ratio , which is computed so ` 100 * (assets-liabs)/liabs `
* Note : health ratio is technically ∞ if liabs are 0
* @param healthType
* @returns health ratio , in percentage form
* /
2022-08-31 02:41:12 -07:00
getHealthRatio ( healthType : HealthType ) : I80F48 | undefined {
return this . accountData ? . healthCache . healthRatio ( healthType ) ;
2022-07-12 03:05:19 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
* Health ratio
* @param healthType
* @returns health ratio , in percentage form , capped to 100
* /
2022-08-31 02:41:12 -07:00
getHealthRatioUi ( healthType : HealthType ) : number | undefined {
const ratio = this . getHealthRatio ( healthType ) ? . toNumber ( ) ;
if ( ratio ) {
return ratio > 100 ? 100 : Math.trunc ( ratio ) ;
} else {
return undefined ;
}
2022-08-11 12:06:01 -07:00
}
2022-07-04 03:29:35 -07:00
/ * *
* Sum of all the assets i . e . token deposits , borrows , total assets in spot open orders , ( perps positions is todo ) in terms of quote value .
2022-08-18 07:19:37 -07:00
* @returns equity , in native quote
2022-07-04 03:29:35 -07:00
* /
2022-08-31 02:41:12 -07:00
getEquity ( ) : I80F48 | undefined {
if ( this . accountData ) {
const equity = this . accountData . equity ;
const total_equity = equity . tokens . reduce (
( a , b ) = > a . add ( b . value ) ,
2022-09-01 00:48:23 -07:00
ZERO_I80F48 ( ) ,
2022-08-31 02:41:12 -07:00
) ;
return total_equity ;
}
return undefined ;
2022-07-04 03:29:35 -07:00
}
/ * *
* The amount of native quote you could withdraw against your existing assets .
2022-08-18 07:19:37 -07:00
* @returns collateral value , in native quote
2022-07-04 03:29:35 -07:00
* /
2022-08-31 02:41:12 -07:00
getCollateralValue ( ) : I80F48 | undefined {
2022-07-12 03:05:19 -07:00
return this . getHealth ( HealthType . init ) ;
2022-07-04 03:29:35 -07:00
}
/ * *
2022-08-11 08:44:12 -07:00
* Sum of all positive assets .
2022-08-18 07:19:37 -07:00
* @returns assets , in native quote
2022-07-04 03:29:35 -07:00
* /
2022-08-31 02:41:12 -07:00
getAssetsValue ( healthType : HealthType ) : I80F48 | undefined {
return this . accountData ? . healthCache . assets ( healthType ) ;
2022-07-04 03:29:35 -07:00
}
/ * *
2022-08-11 08:44:12 -07:00
* Sum of all negative assets .
2022-08-18 07:19:37 -07:00
* @returns liabs , in native quote
2022-07-04 03:29:35 -07:00
* /
2022-08-31 02:41:12 -07:00
getLiabsValue ( healthType : HealthType ) : I80F48 | undefined {
return this . accountData ? . healthCache . liabs ( healthType ) ;
2022-07-04 03:29:35 -07:00
}
/ * *
2022-08-23 04:47:08 -07:00
* The amount of given native token you can withdraw including borrows , considering all existing assets as collateral .
* @returns amount of given native token you can borrow , considering all existing assets as collateral , in native token
2022-07-04 03:29:35 -07:00
* /
2022-08-31 02:41:12 -07:00
getMaxWithdrawWithBorrowForToken (
group : Group ,
mintPk : PublicKey ,
) : I80F48 | undefined {
2022-08-23 04:47:08 -07:00
const tokenBank : Bank = group . getFirstBankByMint ( mintPk ) ;
2022-08-31 02:41:12 -07:00
const initHealth = this . accountData ? . initHealth ;
if ( ! initHealth ) return undefined ;
2022-08-23 04:47:08 -07:00
// Case 1:
// Cannot withdraw if init health is below 0
2022-09-01 00:48:23 -07:00
if ( initHealth . lte ( ZERO_I80F48 ( ) ) ) {
return ZERO_I80F48 ( ) ;
2022-08-23 04:47:08 -07:00
}
// Deposits need special treatment since they would neither count towards liabilities
// nor would be charged loanOriginationFeeRate when withdrawn
const tp = this . findToken ( tokenBank . tokenIndex ) ;
2022-08-31 02:41:12 -07:00
if ( ! tokenBank . price ) return undefined ;
2022-09-01 00:48:23 -07:00
const existingTokenDeposits = tp ? tp . deposits ( tokenBank ) : ZERO_I80F48 ( ) ;
2022-09-13 23:14:46 -07:00
let existingPositionHealthContrib = ZERO_I80F48 ( ) ;
2022-09-01 00:48:23 -07:00
if ( existingTokenDeposits . gt ( ZERO_I80F48 ( ) ) ) {
2022-09-13 23:14:46 -07:00
existingPositionHealthContrib = existingTokenDeposits
. mul ( tokenBank . price )
2022-09-01 00:48:23 -07:00
. imul ( tokenBank . initAssetWeight ) ;
2022-08-23 04:47:08 -07:00
}
// Case 2: token deposits have higher contribution than initHealth,
// can withdraw without borrowing until initHealth reaches 0
if ( existingPositionHealthContrib . gt ( initHealth ) ) {
const withdrawAbleExistingPositionHealthContrib = initHealth ;
// console.log(`initHealth ${initHealth}`);
// console.log(
// `existingPositionHealthContrib ${existingPositionHealthContrib}`,
// );
// console.log(
// `withdrawAbleExistingPositionHealthContrib ${withdrawAbleExistingPositionHealthContrib}`,
// );
return withdrawAbleExistingPositionHealthContrib
. div ( tokenBank . initAssetWeight )
. div ( tokenBank . price ) ;
}
// Case 3: withdraw = withdraw existing deposits + borrows until initHealth reaches 0
const initHealthWithoutExistingPosition = initHealth . sub (
existingPositionHealthContrib ,
) ;
const maxBorrowNative = initHealthWithoutExistingPosition
. div ( tokenBank . initLiabWeight )
. div ( tokenBank . price ) ;
const maxBorrowNativeWithoutFees = maxBorrowNative . div (
2022-09-01 00:48:23 -07:00
ONE_I80F48 ( ) . add ( tokenBank . loanOriginationFeeRate ) ,
2022-08-23 04:47:08 -07:00
) ;
// console.log(`initHealth ${initHealth}`);
// console.log(
// `existingPositionHealthContrib ${existingPositionHealthContrib}`,
// );
// console.log(
// `initHealthWithoutExistingPosition ${initHealthWithoutExistingPosition}`,
// );
// console.log(`maxBorrowNative ${maxBorrowNative}`);
// console.log(`maxBorrowNativeWithoutFees ${maxBorrowNativeWithoutFees}`);
return maxBorrowNativeWithoutFees . add ( existingTokenDeposits ) ;
}
2022-08-31 02:41:12 -07:00
getMaxWithdrawWithBorrowForTokenUi (
group : Group ,
mintPk : PublicKey ,
) : number | undefined {
const maxWithdrawWithBorrow = this . getMaxWithdrawWithBorrowForToken (
group ,
mintPk ,
2022-07-14 05:29:44 -07:00
) ;
2022-08-31 02:41:12 -07:00
if ( maxWithdrawWithBorrow ) {
return toUiDecimals ( maxWithdrawWithBorrow , group . getMintDecimals ( mintPk ) ) ;
} else {
return undefined ;
}
2022-07-04 03:29:35 -07:00
}
2022-07-12 03:05:19 -07:00
/ * *
2022-08-11 08:59:49 -07:00
* The max amount of given source native token you can swap to a target token .
* note : slippageAndFeesFactor is a normalized number , < 1 ,
* e . g . a slippage of 5 % and some fees which are 1 % , then slippageAndFeesFactor = 0.94
* the factor is used to compute how much target can be obtained by swapping source
2022-08-18 07:19:37 -07:00
* @returns max amount of given source native token you can swap to a target token , in native token
2022-07-12 03:05:19 -07:00
* /
getMaxSourceForTokenSwap (
group : Group ,
2022-08-17 23:48:45 -07:00
sourceMintPk : PublicKey ,
targetMintPk : PublicKey ,
2022-07-12 03:05:19 -07:00
slippageAndFeesFactor : number ,
2022-08-31 02:41:12 -07:00
) : I80F48 | undefined {
if ( ! this . accountData ) return undefined ;
2022-08-11 08:44:12 -07:00
return this . accountData . healthCache
. getMaxSourceForTokenSwap (
group ,
2022-08-17 23:48:45 -07:00
sourceMintPk ,
targetMintPk ,
2022-09-01 00:48:23 -07:00
ONE_I80F48 ( ) , // target 1% health
2022-07-12 03:05:19 -07:00
)
2022-08-11 08:44:12 -07:00
. mul ( I80F48 . fromNumber ( slippageAndFeesFactor ) ) ;
2022-07-12 03:05:19 -07:00
}
2022-08-23 07:21:05 -07:00
/ * *
* The max amount of given source ui token you can swap to a target token .
* note : slippageAndFeesFactor is a normalized number , < 1 ,
* e . g . a slippage of 5 % and some fees which are 1 % , then slippageAndFeesFactor = 0.94
* the factor is used to compute how much target can be obtained by swapping source
* @returns max amount of given source ui token you can swap to a target token , in ui token
* /
getMaxSourceUiForTokenSwap (
group : Group ,
sourceMintPk : PublicKey ,
targetMintPk : PublicKey ,
slippageAndFeesFactor : number ,
2022-08-31 02:41:12 -07:00
) : number | undefined {
const maxSource = this . getMaxSourceForTokenSwap (
group ,
sourceMintPk ,
targetMintPk ,
slippageAndFeesFactor ,
2022-08-23 07:21:05 -07:00
) ;
2022-08-31 02:41:12 -07:00
if ( maxSource ) {
return toUiDecimals ( maxSource , group . getMintDecimals ( sourceMintPk ) ) ;
}
2022-08-23 07:21:05 -07:00
}
2022-07-12 03:05:19 -07:00
/ * *
2022-08-11 23:30:13 -07:00
* Simulates new health ratio after applying tokenChanges to the token positions .
2022-08-23 04:47:08 -07:00
* Note : token changes are expected in native amounts
*
2022-08-11 23:30:13 -07:00
* e . g . useful to simulate health after a potential swap .
2022-08-18 07:19:37 -07:00
* Note : health ratio is technically ∞ if liabs are 0
* @returns health ratio , in percentage form
2022-07-12 03:05:19 -07:00
* /
2022-08-11 23:30:13 -07:00
simHealthRatioWithTokenPositionChanges (
2022-07-12 03:05:19 -07:00
group : Group ,
2022-08-23 04:47:08 -07:00
nativeTokenChanges : {
nativeTokenAmount : I80F48 ;
2022-08-17 23:48:45 -07:00
mintPk : PublicKey ;
} [ ] ,
2022-08-11 23:30:13 -07:00
healthType : HealthType = HealthType . init ,
2022-08-31 02:41:12 -07:00
) : I80F48 | undefined {
if ( ! this . accountData ) return undefined ;
2022-08-11 23:30:13 -07:00
return this . accountData . healthCache . simHealthRatioWithTokenPositionChanges (
group ,
2022-08-23 04:47:08 -07:00
nativeTokenChanges ,
healthType ,
) ;
}
/ * *
* Simulates new health ratio after applying tokenChanges to the token positions .
* Note : token changes are expected in ui amounts
*
* e . g . useful to simulate health after a potential swap .
* Note : health ratio is technically ∞ if liabs are 0
* @returns health ratio , in percentage form
* /
simHealthRatioWithTokenPositionUiChanges (
group : Group ,
uiTokenChanges : {
uiTokenAmount : number ;
mintPk : PublicKey ;
} [ ] ,
healthType : HealthType = HealthType . init ,
2022-08-31 02:41:12 -07:00
) : number | undefined {
2022-08-23 04:47:08 -07:00
const nativeTokenChanges = uiTokenChanges . map ( ( tokenChange ) = > {
return {
nativeTokenAmount : toNative (
tokenChange . uiTokenAmount ,
group . getMintDecimals ( tokenChange . mintPk ) ,
) ,
mintPk : tokenChange.mintPk ,
} ;
} ) ;
2022-08-31 02:41:12 -07:00
return this . accountData ? . healthCache
2022-08-23 07:21:05 -07:00
. simHealthRatioWithTokenPositionChanges (
group ,
nativeTokenChanges ,
healthType ,
)
. toNumber ( ) ;
2022-07-12 03:05:19 -07:00
}
2022-08-31 02:36:44 -07:00
public async loadSerum3OpenOrdersForMarket (
client : MangoClient ,
group : Group ,
externalMarketPk : PublicKey ,
) : Promise < Order [ ] > {
const serum3Market = group . serum3MarketsMapByExternal . get (
externalMarketPk . toBase58 ( ) ,
) ;
2022-08-31 02:55:54 -07:00
if ( ! serum3Market ) {
throw new Error (
` Unable to find mint serum3Market for ${ externalMarketPk . toString ( ) } ` ,
) ;
}
2022-08-31 02:36:44 -07:00
const serum3OO = this . serum3Active ( ) . find (
( s ) = > s . marketIndex === serum3Market . marketIndex ,
) ;
if ( ! serum3OO ) {
throw new Error ( ` No open orders account found for ${ externalMarketPk } ` ) ;
}
const serum3MarketExternal = group . serum3MarketExternalsMap . get (
externalMarketPk . toBase58 ( ) ,
) ! ;
const [ bidsInfo , asksInfo ] =
await client . program . provider . connection . getMultipleAccountsInfo ( [
serum3MarketExternal . bidsAddress ,
serum3MarketExternal . asksAddress ,
] ) ;
2022-08-31 02:55:54 -07:00
if ( ! bidsInfo || ! asksInfo ) {
throw new Error (
` bids and asks ai were not fetched for ${ externalMarketPk . toString ( ) } ` ,
) ;
}
2022-08-31 02:36:44 -07:00
const bids = Orderbook . decode ( serum3MarketExternal , bidsInfo . data ) ;
const asks = Orderbook . decode ( serum3MarketExternal , asksInfo . data ) ;
return [ . . . bids , . . . asks ] . filter ( ( o ) = >
o . openOrdersAddress . equals ( serum3OO . openOrders ) ,
) ;
}
2022-07-04 03:29:35 -07:00
/ * *
*
2022-08-31 02:36:44 -07:00
* @param group
* @param serum3Market
* @returns maximum native quote which can be traded for base token given current health
2022-07-04 03:29:35 -07:00
* /
2022-08-31 02:36:44 -07:00
public getMaxQuoteForSerum3Bid (
2022-08-31 02:41:12 -07:00
group : Group ,
2022-08-31 02:36:44 -07:00
serum3Market : Serum3Market ,
) : I80F48 {
2022-08-31 02:55:54 -07:00
if ( ! this . accountData ) {
throw new Error (
` accountData not loaded on MangoAccount, try reloading MangoAccount ` ,
) ;
}
2022-08-31 02:36:44 -07:00
return this . accountData . healthCache . getMaxForSerum3Order (
group ,
serum3Market ,
Serum3Side . bid ,
I80F48 . fromNumber ( 3 ) ,
) ;
}
public getMaxQuoteForSerum3BidUi (
group : Group ,
externalMarketPk : PublicKey ,
) : number {
const serum3Market = group . serum3MarketsMapByExternal . get (
externalMarketPk . toBase58 ( ) ,
) ;
2022-08-31 02:55:54 -07:00
if ( ! serum3Market ) {
throw new Error (
` Unable to find mint serum3Market for ${ externalMarketPk . toString ( ) } ` ,
) ;
}
2022-08-31 02:36:44 -07:00
const nativeAmount = this . getMaxQuoteForSerum3Bid ( group , serum3Market ) ;
return toUiDecimals (
nativeAmount ,
group . getFirstBankByTokenIndex ( serum3Market . quoteTokenIndex ) . mintDecimals ,
) ;
}
/ * *
*
* @param group
* @param serum3Market
* @returns maximum native base which can be traded for quote token given current health
* /
public getMaxBaseForSerum3Ask (
group : Group ,
serum3Market : Serum3Market ,
) : I80F48 {
2022-08-31 02:55:54 -07:00
if ( ! this . accountData ) {
throw new Error (
` accountData not loaded on MangoAccount, try reloading MangoAccount ` ,
) ;
}
2022-08-31 02:36:44 -07:00
return this . accountData . healthCache . getMaxForSerum3Order (
group ,
serum3Market ,
Serum3Side . ask ,
I80F48 . fromNumber ( 3 ) ,
) ;
}
public getMaxBaseForSerum3AskUi (
group : Group ,
externalMarketPk : PublicKey ,
) : number {
const serum3Market = group . serum3MarketsMapByExternal . get (
externalMarketPk . toBase58 ( ) ,
) ;
2022-08-31 02:55:54 -07:00
if ( ! serum3Market ) {
throw new Error (
` Unable to find mint serum3Market for ${ externalMarketPk . toString ( ) } ` ,
) ;
}
2022-08-31 02:36:44 -07:00
const nativeAmount = this . getMaxBaseForSerum3Ask ( group , serum3Market ) ;
return toUiDecimals (
nativeAmount ,
group . getFirstBankByTokenIndex ( serum3Market . baseTokenIndex ) . mintDecimals ,
) ;
}
/ * *
*
* @param group
* @param nativeQuoteAmount
* @param serum3Market
* @param healthType
* @returns health ratio after a bid with nativeQuoteAmount is placed
* /
simHealthRatioWithSerum3BidChanges (
group : Group ,
nativeQuoteAmount : I80F48 ,
serum3Market : Serum3Market ,
healthType : HealthType = HealthType . init ,
) : I80F48 {
2022-08-31 02:55:54 -07:00
if ( ! this . accountData ) {
throw new Error (
` accountData not loaded on MangoAccount, try reloading MangoAccount ` ,
) ;
}
2022-08-31 02:36:44 -07:00
return this . accountData . healthCache . simHealthRatioWithSerum3BidChanges (
group ,
nativeQuoteAmount ,
serum3Market ,
healthType ,
) ;
}
simHealthRatioWithSerum3BidUiChanges (
group : Group ,
uiQuoteAmount : number ,
externalMarketPk : PublicKey ,
healthType : HealthType = HealthType . init ,
) : number {
const serum3Market = group . serum3MarketsMapByExternal . get (
externalMarketPk . toBase58 ( ) ,
) ;
2022-08-31 02:55:54 -07:00
if ( ! serum3Market ) {
throw new Error (
` Unable to find mint serum3Market for ${ externalMarketPk . toString ( ) } ` ,
) ;
}
2022-08-31 02:36:44 -07:00
return this . simHealthRatioWithSerum3BidChanges (
group ,
toNative (
uiQuoteAmount ,
group . getFirstBankByTokenIndex ( serum3Market . quoteTokenIndex )
. mintDecimals ,
) ,
serum3Market ,
healthType ,
) . toNumber ( ) ;
}
/ * *
*
* @param group
* @param nativeBaseAmount
* @param serum3Market
* @param healthType
* @returns health ratio after an ask with nativeBaseAmount is placed
* /
simHealthRatioWithSerum3AskChanges (
group : Group ,
nativeBaseAmount : I80F48 ,
serum3Market : Serum3Market ,
healthType : HealthType = HealthType . init ,
) : I80F48 {
2022-08-31 02:55:54 -07:00
if ( ! this . accountData ) {
throw new Error (
` accountData not loaded on MangoAccount, try reloading MangoAccount ` ,
) ;
}
2022-08-31 02:36:44 -07:00
return this . accountData . healthCache . simHealthRatioWithSerum3AskChanges (
group ,
nativeBaseAmount ,
serum3Market ,
healthType ,
) ;
}
simHealthRatioWithSerum3AskUiChanges (
group : Group ,
uiBaseAmount : number ,
externalMarketPk : PublicKey ,
healthType : HealthType = HealthType . init ,
) : number {
const serum3Market = group . serum3MarketsMapByExternal . get (
externalMarketPk . toBase58 ( ) ,
) ;
2022-08-31 02:55:54 -07:00
if ( ! serum3Market ) {
throw new Error (
` Unable to find mint serum3Market for ${ externalMarketPk . toString ( ) } ` ,
) ;
}
2022-08-31 02:36:44 -07:00
return this . simHealthRatioWithSerum3AskChanges (
group ,
toNative (
uiBaseAmount ,
group . getFirstBankByTokenIndex ( serum3Market . baseTokenIndex )
. mintDecimals ,
) ,
serum3Market ,
healthType ,
) . toNumber ( ) ;
2022-07-04 03:29:35 -07:00
}
2022-09-20 03:57:01 -07:00
public async loadPerpOpenOrdersForMarket (
client : MangoClient ,
2022-08-31 02:41:12 -07:00
group : Group ,
2022-09-20 03:57:01 -07:00
perpMarketName : string ,
) : Promise < PerpOrder [ ] > {
const perpMarket = group . perpMarketsMap . get ( perpMarketName ) ;
if ( ! perpMarket ) {
throw new Error ( ` Perp Market ${ perpMarketName } not found! ` ) ;
}
const [ bids , asks ] = await Promise . all ( [
perpMarket . loadBids ( client ) ,
perpMarket . loadAsks ( client ) ,
] ) ;
return [ . . . Array . from ( bids . items ( ) ) , . . . Array . from ( asks . items ( ) ) ] . filter (
( order ) = > order . owner . equals ( this . publicKey ) ,
) ;
2022-07-04 03:09:33 -07:00
}
2022-06-03 06:34:05 -07:00
toString ( group? : Group ) : string {
2022-07-04 03:29:35 -07:00
let res = 'MangoAccount' ;
res = res + '\n pk: ' + this . publicKey . toString ( ) ;
res = res + '\n name: ' + this . name ;
2022-08-26 01:08:45 -07:00
res = res + '\n owner: ' + this . owner ;
2022-07-05 10:31:47 -07:00
res = res + '\n delegate: ' + this . delegate ;
2022-07-04 03:09:33 -07:00
2022-09-20 03:57:01 -07:00
res =
res +
` \ n max token slots ${ this . tokens . length } , max serum3 slots ${ this . serum3 . length } , max perp slots ${ this . perps . length } , max perp oo slots ${ this . perpOpenOrders . length } ` ;
2022-07-04 03:09:33 -07:00
res =
this . tokensActive ( ) . length > 0
? res +
'\n tokens:' +
JSON . stringify (
2022-08-10 08:17:16 -07:00
this . tokens . map ( ( token , i ) = >
token . isActive ( )
? token . toString ( group , i )
: ` index: ${ i } - empty slot ` ,
) ,
2022-07-04 03:09:33 -07:00
null ,
4 ,
)
: res + '' ;
res =
this . serum3Active ( ) . length > 0
? res + '\n serum:' + JSON . stringify ( this . serum3Active ( ) , null , 4 )
: res + '' ;
res =
this . perpActive ( ) . length > 0
? res + '\n perps:' + JSON . stringify ( this . perpActive ( ) , null , 4 )
: res + '' ;
2022-09-20 03:57:01 -07:00
res =
this . perpOrdersActive ( ) . length > 0
? res +
'\n perps oo:' +
JSON . stringify ( this . perpOrdersActive ( ) , null , 4 )
: res + '' ;
2022-07-04 03:09:33 -07:00
return res ;
2022-05-11 04:33:01 -07:00
}
2022-04-02 23:57:45 -07:00
}
2022-06-22 02:21:02 -07:00
export class TokenPosition {
2022-08-04 10:42:41 -07:00
static TokenIndexUnset = 65535 ;
2022-06-23 07:02:35 -07:00
static from ( dto : TokenPositionDto ) {
2022-06-22 02:21:02 -07:00
return new TokenPosition (
2022-06-23 06:36:08 -07:00
I80F48 . from ( dto . indexedPosition ) ,
2022-04-02 23:57:45 -07:00
dto . tokenIndex ,
dto . inUseCount ,
) ;
}
constructor (
2022-06-22 02:21:02 -07:00
public indexedPosition : I80F48 ,
2022-04-02 23:57:45 -07:00
public tokenIndex : number ,
public inUseCount : number ,
) { }
2022-05-02 09:26:25 -07:00
2022-06-03 06:34:05 -07:00
public isActive ( ) : boolean {
2022-08-10 01:15:28 -07:00
return this . tokenIndex !== TokenPosition . TokenIndexUnset ;
2022-05-02 09:26:25 -07:00
}
2022-06-03 06:34:05 -07:00
2022-08-23 04:47:08 -07:00
/ * *
*
* @param bank
* @returns native balance
* /
public balance ( bank : Bank ) : I80F48 {
2022-06-22 02:21:02 -07:00
if ( this . indexedPosition . isPos ( ) ) {
return bank . depositIndex . mul ( this . indexedPosition ) ;
2022-06-03 06:34:05 -07:00
} else {
2022-06-22 02:21:02 -07:00
return bank . borrowIndex . mul ( this . indexedPosition ) ;
2022-06-03 06:34:05 -07:00
}
}
2022-08-18 07:19:37 -07:00
/ * *
2022-08-23 04:47:08 -07:00
*
2022-08-18 07:19:37 -07:00
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native deposits , 0 if position has borrows
2022-08-18 07:19:37 -07:00
* /
2022-08-23 04:47:08 -07:00
public deposits ( bank : Bank ) : I80F48 {
2022-09-01 00:48:23 -07:00
if ( this . indexedPosition && this . indexedPosition . lt ( ZERO_I80F48 ( ) ) ) {
return ZERO_I80F48 ( ) ;
2022-08-23 04:47:08 -07:00
}
return this . balance ( bank ) ;
2022-06-03 06:34:05 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
2022-08-23 04:47:08 -07:00
*
2022-08-18 07:19:37 -07:00
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native borrows , 0 if position has deposits
2022-08-18 07:19:37 -07:00
* /
2022-08-23 04:47:08 -07:00
public borrows ( bank : Bank ) : I80F48 {
2022-09-01 00:48:23 -07:00
if ( this . indexedPosition && this . indexedPosition . gt ( ZERO_I80F48 ( ) ) ) {
return ZERO_I80F48 ( ) ;
2022-08-18 07:19:37 -07:00
}
2022-08-23 04:47:08 -07:00
return this . balance ( bank ) . abs ( ) ;
}
2022-08-18 07:19:37 -07:00
2022-08-23 04:47:08 -07:00
/ * *
* @param bank
* @returns UI balance , is signed
* /
public balanceUi ( bank : Bank ) : number {
return nativeI80F48ToUi ( this . balance ( bank ) , bank . mintDecimals ) . toNumber ( ) ;
2022-06-29 12:55:39 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
* @param bank
2022-08-23 04:47:08 -07:00
* @returns UI deposits , 0 if position has borrows
2022-08-18 07:19:37 -07:00
* /
2022-08-23 04:47:08 -07:00
public depositsUi ( bank : Bank ) : number {
return nativeI80F48ToUi ( this . deposits ( bank ) , bank . mintDecimals ) . toNumber ( ) ;
}
2022-08-18 07:19:37 -07:00
2022-08-23 04:47:08 -07:00
/ * *
* @param bank
* @returns UI borrows , 0 if position has deposits
* /
public borrowsUi ( bank : Bank ) : number {
return nativeI80F48ToUi ( this . borrows ( bank ) , bank . mintDecimals ) . toNumber ( ) ;
2022-06-29 12:55:39 -07:00
}
2022-08-10 08:17:16 -07:00
public toString ( group? : Group , index? : number ) : string {
2022-08-04 10:42:41 -07:00
let extra = '' ;
2022-06-03 06:34:05 -07:00
if ( group ) {
2022-08-17 23:48:45 -07:00
const bank : Bank = group . getFirstBankByTokenIndex ( this . tokenIndex ) ;
2022-06-03 06:34:05 -07:00
if ( bank ) {
2022-08-23 04:47:08 -07:00
const native = this . balance ( bank ) ;
2022-06-03 06:34:05 -07:00
extra += ', native: ' + native . toNumber ( ) ;
2022-08-18 07:19:37 -07:00
extra += ', ui: ' + this . balanceUi ( bank ) ;
2022-06-03 06:34:05 -07:00
extra += ', tokenName: ' + bank . name ;
}
}
return (
2022-08-10 08:17:16 -07:00
( index !== undefined ? 'index: ' + index : '' ) +
', tokenIndex: ' +
2022-06-03 06:34:05 -07:00
this . tokenIndex +
', inUseCount: ' +
this . inUseCount +
', indexedValue: ' +
2022-06-22 02:21:02 -07:00
this . indexedPosition . toNumber ( ) +
2022-06-03 06:34:05 -07:00
extra
) ;
}
2022-04-02 23:57:45 -07:00
}
2022-06-23 07:02:35 -07:00
export class TokenPositionDto {
2022-04-02 23:57:45 -07:00
constructor (
2022-06-23 06:36:08 -07:00
public indexedPosition : I80F48Dto ,
2022-04-02 23:57:45 -07:00
public tokenIndex : number ,
public inUseCount : number ,
public reserved : number [ ] ,
) { }
}
2022-06-22 02:21:02 -07:00
export class Serum3Orders {
2022-04-03 11:08:56 -07:00
static Serum3MarketIndexUnset = 65535 ;
2022-08-10 01:15:28 -07:00
static from ( dto : Serum3PositionDto ) : Serum3Orders {
2022-06-22 02:21:02 -07:00
return new Serum3Orders (
2022-04-03 07:02:14 -07:00
dto . openOrders ,
dto . marketIndex ,
dto . baseTokenIndex ,
dto . quoteTokenIndex ,
) ;
}
constructor (
public openOrders : PublicKey ,
public marketIndex : number ,
public baseTokenIndex : number ,
public quoteTokenIndex : number ,
) { }
2022-07-04 03:09:33 -07:00
public isActive ( ) : boolean {
return this . marketIndex !== Serum3Orders . Serum3MarketIndexUnset ;
}
2022-04-03 07:02:14 -07:00
}
2022-06-23 07:02:35 -07:00
export class Serum3PositionDto {
2022-04-03 07:02:14 -07:00
constructor (
public openOrders : PublicKey ,
public marketIndex : number ,
public baseTokenIndex : number ,
public quoteTokenIndex : number ,
public reserved : number [ ] ,
) { }
}
2022-05-11 04:33:01 -07:00
2022-08-18 04:45:31 -07:00
export class PerpPosition {
2022-05-11 04:33:01 -07:00
static PerpMarketIndexUnset = 65535 ;
2022-06-23 07:02:35 -07:00
static from ( dto : PerpPositionDto ) {
2022-08-18 04:45:31 -07:00
return new PerpPosition (
2022-05-11 04:33:01 -07:00
dto . marketIndex ,
dto . basePositionLots . toNumber ( ) ,
2022-09-20 03:57:01 -07:00
dto . quotePositionNative . val ,
2022-05-11 04:33:01 -07:00
dto . bidsBaseLots . toNumber ( ) ,
dto . asksBaseLots . toNumber ( ) ,
dto . takerBaseLots . toNumber ( ) ,
dto . takerQuoteLots . toNumber ( ) ,
) ;
}
constructor (
public marketIndex : number ,
public basePositionLots : number ,
2022-09-20 03:57:01 -07:00
public quotePositionNative : BN ,
2022-05-11 04:33:01 -07:00
public bidsBaseLots : number ,
public asksBaseLots : number ,
public takerBaseLots : number ,
public takerQuoteLots : number ,
) { }
2022-07-04 03:09:33 -07:00
isActive ( ) : boolean {
2022-08-18 04:45:31 -07:00
return this . marketIndex != PerpPosition . PerpMarketIndexUnset ;
2022-07-04 03:09:33 -07:00
}
2022-05-11 04:33:01 -07:00
}
2022-06-23 07:02:35 -07:00
export class PerpPositionDto {
2022-05-11 04:33:01 -07:00
constructor (
public marketIndex : number ,
public reserved : [ ] ,
public basePositionLots : BN ,
public quotePositionNative : { val : BN } ,
public bidsBaseLots : BN ,
public asksBaseLots : BN ,
public takerBaseLots : BN ,
public takerQuoteLots : BN ,
) { }
}
2022-07-04 03:29:35 -07:00
2022-09-20 03:57:01 -07:00
export class PerpOo {
static OrderMarketUnset = 65535 ;
static from ( dto : PerpOoDto ) {
return new PerpOo (
dto . orderSide ,
dto . orderMarket ,
dto . clientOrderId . toNumber ( ) ,
dto . orderId ,
) ;
}
constructor (
public orderSide : any ,
public orderMarket : 0 ,
public clientOrderId : number ,
public orderId : BN ,
) { }
}
export class PerpOoDto {
constructor (
public orderSide : any ,
public orderMarket : 0 ,
public clientOrderId : BN ,
public orderId : BN ,
) { }
}
2022-07-04 03:29:35 -07:00
export class HealthType {
static maint = { maint : { } } ;
static init = { init : { } } ;
}
export class MangoAccountData {
constructor (
2022-07-13 10:18:55 -07:00
public healthCache : HealthCache ,
2022-07-04 03:29:35 -07:00
public initHealth : I80F48 ,
public maintHealth : I80F48 ,
public equity : Equity ,
) { }
static from ( event : {
2022-07-13 10:18:55 -07:00
healthCache : HealthCacheDto ;
2022-07-04 03:29:35 -07:00
initHealth : I80F48Dto ;
maintHealth : I80F48Dto ;
equity : {
tokens : [ { tokenIndex : number ; value : I80F48Dto } ] ;
perps : [ { perpMarketIndex : number ; value : I80F48Dto } ] ;
} ;
initHealthLiabs : I80F48Dto ;
tokenAssets : any ;
} ) {
return new MangoAccountData (
2022-08-31 02:36:44 -07:00
HealthCache . fromDto ( event . healthCache ) ,
2022-07-04 03:29:35 -07:00
I80F48 . from ( event . initHealth ) ,
I80F48 . from ( event . maintHealth ) ,
Equity . from ( event . equity ) ,
) ;
}
}
export class Equity {
public constructor (
public tokens : TokenEquity [ ] ,
public perps : PerpEquity [ ] ,
) { }
static from ( dto : EquityDto ) : Equity {
return new Equity (
dto . tokens . map (
( token ) = > new TokenEquity ( token . tokenIndex , I80F48 . from ( token . value ) ) ,
) ,
dto . perps . map (
( perpAccount ) = >
new PerpEquity (
perpAccount . perpMarketIndex ,
I80F48 . from ( perpAccount . value ) ,
) ,
) ,
) ;
}
}
export class TokenEquity {
public constructor ( public tokenIndex : number , public value : I80F48 ) { }
}
export class PerpEquity {
public constructor ( public perpMarketIndex : number , public value : I80F48 ) { }
}
export class EquityDto {
tokens : { tokenIndex : number ; value : I80F48Dto } [ ] ;
perps : { perpMarketIndex : number ; value : I80F48Dto } [ ] ;
}