client functions via program simulation
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
39284c5705
commit
dff3f7cd8c
|
@ -0,0 +1,30 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::state::{PerpMarketIndex, TokenIndex};
|
||||
|
||||
#[event]
|
||||
#[derive(Debug)]
|
||||
pub struct MangoAccountData {
|
||||
pub init_health: I80F48,
|
||||
pub maint_health: I80F48,
|
||||
pub equity: Equity,
|
||||
}
|
||||
|
||||
#[derive(AnchorDeserialize, AnchorSerialize, Debug)]
|
||||
pub struct Equity {
|
||||
pub tokens: Vec<TokenEquity>,
|
||||
pub perps: Vec<PerpEquity>,
|
||||
}
|
||||
|
||||
#[derive(AnchorDeserialize, AnchorSerialize, Debug)]
|
||||
pub struct TokenEquity {
|
||||
pub token_index: TokenIndex,
|
||||
pub value: I80F48, // in native quote
|
||||
}
|
||||
|
||||
#[derive(AnchorDeserialize, AnchorSerialize, Debug)]
|
||||
pub struct PerpEquity {
|
||||
pub perp_market_index: PerpMarketIndex,
|
||||
value: I80F48, // in native quote
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
use crate::{events::MangoAccountData, state::*};
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ComputeAccountData<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(
|
||||
has_one = group,
|
||||
)]
|
||||
pub account: AccountLoader<'info, MangoAccount>,
|
||||
}
|
||||
|
||||
pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
|
||||
let group_pk = ctx.accounts.group.key();
|
||||
let account = ctx.accounts.account.load()?;
|
||||
|
||||
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group_pk)?;
|
||||
|
||||
let init_health = compute_health(&account, HealthType::Init, &account_retriever)?;
|
||||
let maint_health = compute_health(&account, HealthType::Maint, &account_retriever)?;
|
||||
|
||||
let equity = compute_equity(&account, &account_retriever)?;
|
||||
|
||||
emit!(MangoAccountData {
|
||||
init_health,
|
||||
maint_health,
|
||||
equity
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use crate::state::*;
|
||||
use anchor_lang::prelude::*;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ComputeHealth<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(
|
||||
has_one = group,
|
||||
)]
|
||||
pub account: AccountLoader<'info, MangoAccount>,
|
||||
}
|
||||
|
||||
pub fn compute_health(ctx: Context<ComputeHealth>, health_type: HealthType) -> Result<I80F48> {
|
||||
let account = ctx.accounts.account.load()?;
|
||||
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
|
||||
let health = crate::state::compute_health(&account, health_type, &retriever)?;
|
||||
msg!("health: {}", health);
|
||||
|
||||
Ok(health)
|
||||
}
|
|
@ -2,7 +2,7 @@ pub use benchmark::*;
|
|||
pub use close_account::*;
|
||||
pub use close_group::*;
|
||||
pub use close_stub_oracle::*;
|
||||
pub use compute_health::*;
|
||||
pub use compute_account_data::*;
|
||||
pub use create_account::*;
|
||||
pub use create_group::*;
|
||||
pub use create_stub_oracle::*;
|
||||
|
@ -40,7 +40,7 @@ mod benchmark;
|
|||
mod close_account;
|
||||
mod close_group;
|
||||
mod close_stub_oracle;
|
||||
mod compute_health;
|
||||
mod compute_account_data;
|
||||
mod create_account;
|
||||
mod create_group;
|
||||
mod create_stub_oracle;
|
||||
|
|
|
@ -12,15 +12,14 @@ use instructions::*;
|
|||
pub mod accounts_zerocopy;
|
||||
pub mod address_lookup_table;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod instructions;
|
||||
pub mod logs;
|
||||
mod serum3_cpi;
|
||||
pub mod state;
|
||||
pub mod types;
|
||||
|
||||
use state::{
|
||||
HealthType, OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex,
|
||||
};
|
||||
use state::{OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex};
|
||||
|
||||
declare_id!("m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD");
|
||||
|
||||
|
@ -383,8 +382,8 @@ pub mod mango_v4 {
|
|||
|
||||
// resolve_banktruptcy
|
||||
|
||||
pub fn compute_health(ctx: Context<ComputeHealth>, health_type: HealthType) -> Result<I80F48> {
|
||||
instructions::compute_health(ctx, health_type)
|
||||
pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
|
||||
instructions::compute_account_data(ctx)
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use checked_math as cm;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::events::{Equity, TokenEquity};
|
||||
|
||||
use super::{MangoAccount, ScanningAccountRetriever};
|
||||
|
||||
pub fn compute_equity(
|
||||
account: &MangoAccount,
|
||||
retriever: &ScanningAccountRetriever,
|
||||
) -> Result<Equity> {
|
||||
let mut token_equity_map = HashMap::new();
|
||||
|
||||
// token contributions
|
||||
for (_i, position) in account.tokens.iter_active().enumerate() {
|
||||
let (bank, oracle_price) = retriever.scanned_bank_and_oracle(position.token_index)?;
|
||||
// converts the token value to the basis token value for health computations
|
||||
// TODO: health basis token == USDC?
|
||||
let native = position.native(bank);
|
||||
token_equity_map.insert(bank.token_index, native * oracle_price);
|
||||
}
|
||||
|
||||
// token contributions from Serum3
|
||||
for (_i, serum_account) in account.serum3.iter_active().enumerate() {
|
||||
let oo = retriever.scanned_serum_oo(&serum_account.open_orders)?;
|
||||
|
||||
// note base token value
|
||||
let (_bank, oracle_price) =
|
||||
retriever.scanned_bank_and_oracle(serum_account.base_token_index)?;
|
||||
let accumulated_equity = token_equity_map
|
||||
.get(&serum_account.base_token_index)
|
||||
.unwrap_or(&I80F48::ZERO);
|
||||
let native_coin_total_i80f48 =
|
||||
I80F48::from_num(oo.native_coin_total + oo.referrer_rebates_accrued);
|
||||
let new_equity = cm!(accumulated_equity + native_coin_total_i80f48 * oracle_price);
|
||||
token_equity_map.insert(serum_account.base_token_index, new_equity);
|
||||
|
||||
// note quote token value
|
||||
let (_bank, oracle_price) =
|
||||
retriever.scanned_bank_and_oracle(serum_account.quote_token_index)?;
|
||||
let accumulated_equity = token_equity_map
|
||||
.get(&serum_account.quote_token_index)
|
||||
.unwrap_or(&I80F48::ZERO);
|
||||
let native_pc_total_i80f48 = I80F48::from_num(oo.native_pc_total);
|
||||
let new_equity = cm!(accumulated_equity + native_pc_total_i80f48 * oracle_price);
|
||||
token_equity_map.insert(serum_account.quote_token_index, new_equity);
|
||||
}
|
||||
|
||||
let tokens = token_equity_map
|
||||
.iter()
|
||||
.map(|tuple| TokenEquity {
|
||||
token_index: *tuple.0,
|
||||
value: *tuple.1,
|
||||
})
|
||||
.collect::<Vec<TokenEquity>>();
|
||||
|
||||
// TODO: perp contributions
|
||||
let perps = Vec::new();
|
||||
|
||||
Ok(Equity { tokens, perps })
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
use serum_dex::state::OpenOrders;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
|
@ -244,15 +246,8 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
|
|||
Ok((bank1, bank2, price1, price2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'info> AccountRetriever for ScanningAccountRetriever<'a, 'info> {
|
||||
fn bank_and_oracle(
|
||||
&self,
|
||||
_group: &Pubkey,
|
||||
_account_index: usize,
|
||||
token_index: TokenIndex,
|
||||
) -> Result<(&Bank, I80F48)> {
|
||||
pub fn scanned_bank_and_oracle(&self, token_index: TokenIndex) -> Result<(&Bank, I80F48)> {
|
||||
let index = self.bank_index(token_index)?;
|
||||
let bank = self.ais[index].load_fully_unchecked::<Bank>()?;
|
||||
let oracle = &self.ais[cm!(self.n_banks() + index)];
|
||||
|
@ -263,22 +258,41 @@ impl<'a, 'info> AccountRetriever for ScanningAccountRetriever<'a, 'info> {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn scanned_perp_market(&self, perp_market_index: PerpMarketIndex) -> Result<&PerpMarket> {
|
||||
let index = self.perp_market_index(perp_market_index)?;
|
||||
self.ais[index].load_fully_unchecked::<PerpMarket>()
|
||||
}
|
||||
|
||||
pub fn scanned_serum_oo(&self, key: &Pubkey) -> Result<&OpenOrders> {
|
||||
let oo = self.ais[self.begin_serum3()..]
|
||||
.iter()
|
||||
.find(|ai| ai.key == key)
|
||||
.ok_or_else(|| error!(MangoError::SomeError))?;
|
||||
serum3_cpi::load_open_orders(oo)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'info> AccountRetriever for ScanningAccountRetriever<'a, 'info> {
|
||||
fn bank_and_oracle(
|
||||
&self,
|
||||
_group: &Pubkey,
|
||||
_account_index: usize,
|
||||
token_index: TokenIndex,
|
||||
) -> Result<(&Bank, I80F48)> {
|
||||
self.scanned_bank_and_oracle(token_index)
|
||||
}
|
||||
|
||||
fn perp_market(
|
||||
&self,
|
||||
_group: &Pubkey,
|
||||
_account_index: usize,
|
||||
perp_market_index: PerpMarketIndex,
|
||||
) -> Result<&PerpMarket> {
|
||||
let index = self.perp_market_index(perp_market_index)?;
|
||||
self.ais[index].load_fully_unchecked::<PerpMarket>()
|
||||
self.scanned_perp_market(perp_market_index)
|
||||
}
|
||||
|
||||
fn serum_oo(&self, _account_index: usize, key: &Pubkey) -> Result<&OpenOrders> {
|
||||
let oo = self.ais[self.begin_serum3()..]
|
||||
.iter()
|
||||
.find(|ai| ai.key == key)
|
||||
.ok_or_else(|| error!(MangoError::SomeError))?;
|
||||
serum3_cpi::load_open_orders(oo)
|
||||
self.scanned_serum_oo(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,6 +453,7 @@ impl PerpInfo {
|
|||
(HealthType::Maint, true) => self.maint_liab_weight,
|
||||
(HealthType::Maint, false) => self.maint_asset_weight,
|
||||
};
|
||||
|
||||
// FUTURE: Allow v3-style "reliable" markets where we can return
|
||||
// `self.quote + weight * self.base` here
|
||||
cm!(self.quote + weight * self.base).min(I80F48::ZERO)
|
||||
|
@ -569,6 +584,7 @@ pub fn new_health_cache(
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: also account for perp funding in health
|
||||
// health contribution from perp accounts
|
||||
let mut perp_infos = Vec::with_capacity(account.perps.iter_active_accounts().count());
|
||||
for (i, perp_account) in account.perps.iter_active_accounts().enumerate() {
|
||||
|
@ -576,6 +592,7 @@ pub fn new_health_cache(
|
|||
|
||||
// find the TokenInfos for the market's base and quote tokens
|
||||
let base_index = find_token_info_index(&token_infos, perp_market.base_token_index)?;
|
||||
// TODO: base_index could be unset
|
||||
let base_info = &token_infos[base_index];
|
||||
|
||||
let base_lot_size = I80F48::from(perp_market.base_lot_size);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub use bank::*;
|
||||
pub use equity::*;
|
||||
pub use group::*;
|
||||
pub use health::*;
|
||||
pub use mango_account::*;
|
||||
|
@ -9,6 +10,7 @@ pub use perp_market::*;
|
|||
pub use serum3_market::*;
|
||||
|
||||
mod bank;
|
||||
mod equity;
|
||||
mod group;
|
||||
mod health;
|
||||
mod mango_account;
|
||||
|
|
|
@ -2423,22 +2423,20 @@ impl ClientInstruction for UpdateIndexInstruction {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ComputeHealthInstruction {
|
||||
pub struct ComputeAccountDataInstruction {
|
||||
pub account: Pubkey,
|
||||
pub health_type: HealthType,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for ComputeHealthInstruction {
|
||||
type Accounts = mango_v4::accounts::ComputeHealth;
|
||||
type Instruction = mango_v4::instruction::ComputeHealth;
|
||||
impl ClientInstruction for ComputeAccountDataInstruction {
|
||||
type Accounts = mango_v4::accounts::ComputeAccountData;
|
||||
type Instruction = mango_v4::instruction::ComputeAccountData;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {
|
||||
health_type: self.health_type,
|
||||
};
|
||||
let instruction = Self::Instruction {};
|
||||
|
||||
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
//
|
||||
send_tx(
|
||||
solana,
|
||||
ComputeHealthInstruction {
|
||||
ComputeAccountDataInstruction {
|
||||
account,
|
||||
health_type: HealthType::Init,
|
||||
},
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { BN } from '@project-serum/anchor';
|
||||
import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes';
|
||||
import { PythHttpClient } from '@pythnetwork/client';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { nativeI80F48ToUi } from '../utils';
|
||||
import { I80F48, I80F48Dto, ZERO_I80F48 } from './I80F48';
|
||||
|
@ -22,7 +21,12 @@ export class Bank {
|
|||
public rate1: I80F48;
|
||||
public util0: I80F48;
|
||||
public util1: I80F48;
|
||||
public price: number;
|
||||
public price: I80F48;
|
||||
public initAssetWeight: I80F48;
|
||||
public maintAssetWeight: I80F48;
|
||||
public initLiabWeight: I80F48;
|
||||
public maintLiabWeight: I80F48;
|
||||
public liquidationFee: I80F48;
|
||||
|
||||
static from(
|
||||
publicKey: PublicKey,
|
||||
|
@ -128,6 +132,11 @@ export class Bank {
|
|||
this.rate0 = I80F48.from(rate0);
|
||||
this.util1 = I80F48.from(util1);
|
||||
this.rate1 = I80F48.from(rate1);
|
||||
this.maintAssetWeight = I80F48.from(maintAssetWeight);
|
||||
this.initAssetWeight = I80F48.from(initAssetWeight);
|
||||
this.maintLiabWeight = I80F48.from(maintLiabWeight);
|
||||
this.initLiabWeight = I80F48.from(initLiabWeight);
|
||||
this.liquidationFee = I80F48.from(liquidationFee);
|
||||
this.price = undefined;
|
||||
}
|
||||
|
||||
|
@ -203,13 +212,6 @@ export class Bank {
|
|||
const utilization = totalBorrows.div(totalDeposits);
|
||||
return utilization.mul(borrowRate);
|
||||
}
|
||||
|
||||
async getOraclePrice(connection) {
|
||||
const pythClient = new PythHttpClient(connection, this.oracle);
|
||||
const data = await pythClient.getData();
|
||||
|
||||
return data.productPrice;
|
||||
}
|
||||
}
|
||||
|
||||
export class MintInfo {
|
||||
|
@ -246,10 +248,27 @@ export class MintInfo {
|
|||
public tokenIndex: number,
|
||||
) {}
|
||||
|
||||
public firstBank() {
|
||||
public firstBank(): PublicKey {
|
||||
return this.banks[0];
|
||||
}
|
||||
public firstVault() {
|
||||
public firstVault(): PublicKey {
|
||||
return this.vaults[0];
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let res =
|
||||
'mint ' +
|
||||
this.mint.toBase58() +
|
||||
'\n oracle ' +
|
||||
this.oracle.toBase58() +
|
||||
'\n banks ' +
|
||||
this.banks
|
||||
.filter((pk) => pk.toBase58() !== PublicKey.default.toBase58())
|
||||
.toString() +
|
||||
'\n vaults ' +
|
||||
this.vaults
|
||||
.filter((pk) => pk.toBase58() !== PublicKey.default.toBase58())
|
||||
.toString();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { Market } from '@project-serum/serum';
|
||||
import { parsePriceData, PriceData } from '@pythnetwork/client';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { MangoClient } from '../client';
|
||||
import { SERUM3_PROGRAM_ID } from '../constants';
|
||||
import { Id } from '../ids';
|
||||
import { Bank, MintInfo } from './bank';
|
||||
import { I80F48, ONE_I80F48 } from './I80F48';
|
||||
import { PerpMarket } from './perp';
|
||||
import { Serum3Market } from './serum3';
|
||||
|
||||
|
@ -21,6 +23,7 @@ export class Group {
|
|||
new Map(),
|
||||
new Map(),
|
||||
new Map(),
|
||||
new Map(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -33,6 +36,7 @@ export class Group {
|
|||
public serum3MarketExternalsMap: Map<string, Market>,
|
||||
public perpMarketsMap: Map<string, PerpMarket>,
|
||||
public mintInfosMap: Map<number, MintInfo>,
|
||||
public oraclesMap: Map<string, PriceData>,
|
||||
) {}
|
||||
|
||||
public findBank(tokenIndex: number): Bank | undefined {
|
||||
|
@ -61,8 +65,13 @@ export class Group {
|
|||
this.reloadSerum3Markets(client, ids),
|
||||
this.reloadPerpMarkets(client, ids),
|
||||
]);
|
||||
// requires reloadSerum3Markets to have finished loading
|
||||
await this.reloadSerum3ExternalMarkets(client, ids);
|
||||
|
||||
await Promise.all([
|
||||
// requires reloadBanks to have finished loading
|
||||
this.reloadBankPrices(client, ids),
|
||||
// requires reloadSerum3Markets to have finished loading
|
||||
this.reloadSerum3ExternalMarkets(client, ids),
|
||||
]);
|
||||
// console.timeEnd('group.reload');
|
||||
}
|
||||
|
||||
|
@ -80,7 +89,6 @@ export class Group {
|
|||
}
|
||||
|
||||
this.banksMap = new Map(banks.map((bank) => [bank.name, bank]));
|
||||
client.getPricesForGroup(this);
|
||||
}
|
||||
|
||||
public async reloadMintInfos(client: MangoClient, ids?: Id) {
|
||||
|
@ -159,4 +167,39 @@ export class Group {
|
|||
perpMarkets.map((perpMarket) => [perpMarket.name, perpMarket]),
|
||||
);
|
||||
}
|
||||
|
||||
public async reloadBankPrices(client: MangoClient, ids?: Id): Promise<void> {
|
||||
const banks = Array.from(this?.banksMap, ([, value]) => value);
|
||||
const oracles = banks.map((b) => b.oracle);
|
||||
console.log(oracles.toString());
|
||||
const prices =
|
||||
await client.program.provider.connection.getMultipleAccountsInfo(oracles);
|
||||
|
||||
for (const [index, price] of prices.entries()) {
|
||||
if (banks[index].name === 'USDC') {
|
||||
banks[index].price = ONE_I80F48;
|
||||
} else {
|
||||
banks[index].price = I80F48.fromNumber(
|
||||
parsePriceData(price.data).previousPrice,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let res = 'Group\n';
|
||||
res = res + ' pk: ' + this.publicKey.toString();
|
||||
|
||||
res =
|
||||
res +
|
||||
'\n mintInfos:' +
|
||||
Array.from(this.mintInfosMap.entries())
|
||||
.map(
|
||||
(mintInfoTuple) =>
|
||||
' \n' + mintInfoTuple[0] + ') ' + mintInfoTuple[1].toString(),
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { MangoClient } from '../client';
|
|||
import { nativeI80F48ToUi } from '../utils';
|
||||
import { Bank } from './bank';
|
||||
import { Group } from './group';
|
||||
import { I80F48, I80F48Dto, ZERO_I80F48 } from './I80F48';
|
||||
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from './I80F48';
|
||||
export class MangoAccount {
|
||||
public tokens: TokenPosition[];
|
||||
public serum3: Serum3Orders[];
|
||||
|
@ -43,6 +43,7 @@ export class MangoAccount {
|
|||
obj.accountNum,
|
||||
obj.bump,
|
||||
obj.reserved,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -60,6 +61,7 @@ export class MangoAccount {
|
|||
accountNum: number,
|
||||
bump: number,
|
||||
reserved: number[],
|
||||
public accountData: {},
|
||||
) {
|
||||
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||
this.tokens = tokens.values.map((dto) => TokenPosition.from(dto));
|
||||
|
@ -67,8 +69,13 @@ export class MangoAccount {
|
|||
this.perps = perps.accounts.map((dto) => PerpPositions.from(dto));
|
||||
}
|
||||
|
||||
async reload(client: MangoClient) {
|
||||
async reload(client: MangoClient, group: Group) {
|
||||
Object.assign(this, await client.getMangoAccount(this));
|
||||
await this.reloadAccountData(client, group);
|
||||
}
|
||||
|
||||
async reloadAccountData(client: MangoClient, group: Group) {
|
||||
this.accountData = await client.computeAccountData(group, this);
|
||||
}
|
||||
|
||||
findToken(tokenIndex: number): TokenPosition | undefined {
|
||||
|
@ -84,6 +91,16 @@ export class MangoAccount {
|
|||
return ta ? ta.native(bank) : ZERO_I80F48;
|
||||
}
|
||||
|
||||
getNativeDeposits(bank: Bank): I80F48 {
|
||||
const native = this.getNative(bank);
|
||||
return native.gte(ZERO_I80F48) ? native : ZERO_I80F48;
|
||||
}
|
||||
|
||||
getNativeBorrows(bank: Bank): I80F48 {
|
||||
const native = this.getNative(bank);
|
||||
return native.lte(ZERO_I80F48) ? native : ZERO_I80F48;
|
||||
}
|
||||
|
||||
getUi(bank: Bank): number {
|
||||
const ta = this.findToken(bank.tokenIndex);
|
||||
return ta ? ta.ui(bank) : 0;
|
||||
|
@ -99,6 +116,100 @@ export class MangoAccount {
|
|||
return ta ? ta.uiBorrows(bank) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
getEquity(): I80F48 {
|
||||
const equity = (this.accountData as MangoAccountData).equity;
|
||||
let total_equity = equity.tokens.reduce(
|
||||
(a, b) => a.add(b.value),
|
||||
ZERO_I80F48,
|
||||
);
|
||||
return total_equity;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of native quote you could withdraw against your existing assets.
|
||||
*/
|
||||
getCollateralValue(): I80F48 {
|
||||
return (this.accountData as MangoAccountData).initHealth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to getEquity, but only the sum of all positive assets.
|
||||
*/
|
||||
getAssetsVal(): I80F48 {
|
||||
const equity = (this.accountData as MangoAccountData).equity;
|
||||
let total_equity = equity.tokens.reduce(
|
||||
(a, b) => (b.value.gt(ZERO_I80F48) ? a.add(b.value) : a),
|
||||
ZERO_I80F48,
|
||||
);
|
||||
return total_equity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to getEquity, but only the sum of all negative assets. Note: return value would be negative.
|
||||
*/
|
||||
getLiabsVal(): I80F48 {
|
||||
const equity = (this.accountData as MangoAccountData).equity;
|
||||
let total_equity = equity.tokens.reduce(
|
||||
(a, b) => (b.value.lt(ZERO_I80F48) ? a.add(b.value) : a),
|
||||
ZERO_I80F48,
|
||||
);
|
||||
return total_equity;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of given native token you can borrow, considering all existing assets as collateral except the deposits for this token.
|
||||
* The existing native deposits need to be added to get the full amount that could be withdrawn.
|
||||
*/
|
||||
async getMaxWithdrawWithBorrowForToken(
|
||||
group: Group,
|
||||
tokenName: string,
|
||||
): Promise<I80F48> {
|
||||
const bank = group.banksMap.get(tokenName);
|
||||
|
||||
const initHealth = (this.accountData as MangoAccountData).initHealth;
|
||||
|
||||
const newInitHealth = initHealth.sub(
|
||||
this.getNativeDeposits(bank).mul(bank.price).mul(bank.initAssetWeight),
|
||||
);
|
||||
|
||||
return newInitHealth.div(bank.price.mul(bank.initLiabWeight));
|
||||
}
|
||||
|
||||
/**
|
||||
* The remaining native quote margin available for given market.
|
||||
*
|
||||
* TODO: this is a very bad estimation atm.
|
||||
* It assumes quote asset is always USDC,
|
||||
* it assumes that there are no interaction effects,
|
||||
* it assumes that there are no existing borrows for either of the tokens in the market.
|
||||
*/
|
||||
getSerum3MarketMarginAvailable(group: Group, marketName: string): I80F48 {
|
||||
const initHealth = (this.accountData as MangoAccountData).initHealth;
|
||||
const serum3Market = group.serum3MarketsMap.get(marketName)!;
|
||||
const marketAssetWeight = group.findBank(
|
||||
serum3Market.baseTokenIndex,
|
||||
).initAssetWeight;
|
||||
return initHealth.div(ONE_I80F48.sub(marketAssetWeight));
|
||||
}
|
||||
|
||||
/**
|
||||
* The remaining native quote margin available for given market.
|
||||
*
|
||||
* TODO: this is a very bad estimation atm.
|
||||
* It assumes quote asset is always USDC,
|
||||
* it assumes that there are no interaction effects,
|
||||
* it assumes that there are no existing borrows for either of the tokens in the market.
|
||||
*/
|
||||
getPerpMarketMarginAvailable(group: Group, marketName: string): I80F48 {
|
||||
const initHealth = (this.accountData as MangoAccountData).initHealth;
|
||||
const perpMarket = group.perpMarketsMap.get(marketName)!;
|
||||
const marketAssetWeight = perpMarket.initAssetWeight;
|
||||
return initHealth.div(ONE_I80F48.sub(marketAssetWeight));
|
||||
}
|
||||
|
||||
tokensActive(): TokenPosition[] {
|
||||
return this.tokens.filter((token) => token.isActive());
|
||||
}
|
||||
|
@ -112,8 +223,9 @@ export class MangoAccount {
|
|||
}
|
||||
|
||||
toString(group?: Group): string {
|
||||
let res = '';
|
||||
res = res + ' name: ' + this.name;
|
||||
let res = 'MangoAccount';
|
||||
res = res + '\n pk: ' + this.publicKey.toString();
|
||||
res = res + '\n name: ' + this.name;
|
||||
|
||||
res =
|
||||
this.tokensActive().length > 0
|
||||
|
@ -293,3 +405,68 @@ export class PerpPositionDto {
|
|||
public takerQuoteLots: BN,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class HealthType {
|
||||
static maint = { maint: {} };
|
||||
static init = { init: {} };
|
||||
}
|
||||
|
||||
export class MangoAccountData {
|
||||
constructor(
|
||||
public initHealth: I80F48,
|
||||
public maintHealth: I80F48,
|
||||
public equity: Equity,
|
||||
) {}
|
||||
|
||||
static from(event: {
|
||||
initHealth: I80F48Dto;
|
||||
maintHealth: I80F48Dto;
|
||||
equity: {
|
||||
tokens: [{ tokenIndex: number; value: I80F48Dto }];
|
||||
perps: [{ perpMarketIndex: number; value: I80F48Dto }];
|
||||
};
|
||||
initHealthLiabs: I80F48Dto;
|
||||
tokenAssets: any;
|
||||
}) {
|
||||
return new MangoAccountData(
|
||||
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 }[];
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
initializeAccount,
|
||||
WRAPPED_SOL_MINT,
|
||||
} from '@project-serum/serum/lib/token-instructions';
|
||||
import { parsePriceData } from '@pythnetwork/client';
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
|
@ -31,7 +30,7 @@ import bs58 from 'bs58';
|
|||
import { Bank, MintInfo } from './accounts/bank';
|
||||
import { Group } from './accounts/group';
|
||||
import { I80F48 } from './accounts/I80F48';
|
||||
import { MangoAccount } from './accounts/mangoAccount';
|
||||
import { MangoAccount, MangoAccountData } from './accounts/mangoAccount';
|
||||
import { StubOracle } from './accounts/oracle';
|
||||
import { OrderType, PerpMarket, Side } from './accounts/perp';
|
||||
import {
|
||||
|
@ -282,25 +281,6 @@ export class MangoClient {
|
|||
});
|
||||
}
|
||||
|
||||
public async getPricesForGroup(group: Group): Promise<void> {
|
||||
if (group.banksMap.size === 0) {
|
||||
await this.getBanksForGroup(group);
|
||||
}
|
||||
|
||||
const banks = Array.from(group?.banksMap, ([, value]) => value);
|
||||
const oracles = banks.map((b) => b.oracle);
|
||||
const prices =
|
||||
await this.program.provider.connection.getMultipleAccountsInfo(oracles);
|
||||
|
||||
for (const [index, price] of prices.entries()) {
|
||||
if (banks[index].name === 'USDC') {
|
||||
banks[index].price = 1;
|
||||
} else {
|
||||
banks[index].price = parsePriceData(price.data).previousPrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stub Oracle
|
||||
|
||||
public async createStubOracle(
|
||||
|
@ -454,6 +434,32 @@ export class MangoClient {
|
|||
.rpc();
|
||||
}
|
||||
|
||||
public async computeAccountData(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
): Promise<MangoAccountData> {
|
||||
const healthRemainingAccounts: PublicKey[] =
|
||||
await this.buildHealthRemainingAccounts(group, mangoAccount);
|
||||
|
||||
const res = await this.program.methods
|
||||
.computeAccountData()
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
account: mangoAccount.publicKey,
|
||||
})
|
||||
.remainingAccounts(
|
||||
healthRemainingAccounts.map(
|
||||
(pk) =>
|
||||
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
|
||||
),
|
||||
)
|
||||
.simulate();
|
||||
|
||||
return MangoAccountData.from(
|
||||
res.events.find((event) => (event.name = 'MangoAccountData')).data as any,
|
||||
);
|
||||
}
|
||||
|
||||
public async tokenDeposit(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
|
|
|
@ -2141,7 +2141,7 @@ export type MangoV4 = {
|
|||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "computeHealth",
|
||||
"name": "computeAccountData",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
|
@ -2154,17 +2154,7 @@ export type MangoV4 = {
|
|||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "healthType",
|
||||
"type": {
|
||||
"defined": "HealthType"
|
||||
}
|
||||
}
|
||||
],
|
||||
"returns": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "benchmark",
|
||||
|
@ -2875,6 +2865,66 @@ export type MangoV4 = {
|
|||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "Equity",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "tokens",
|
||||
"type": {
|
||||
"vec": {
|
||||
"defined": "TokenEquity"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "perps",
|
||||
"type": {
|
||||
"vec": {
|
||||
"defined": "PerpEquity"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TokenEquity",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "PerpEquity",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "perpMarketIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "FlashLoanWithdraw",
|
||||
"type": {
|
||||
|
@ -3587,6 +3637,32 @@ export type MangoV4 = {
|
|||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"name": "MangoAccountData",
|
||||
"fields": [
|
||||
{
|
||||
"name": "initHealth",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "maintHealth",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "equity",
|
||||
"type": {
|
||||
"defined": "Equity"
|
||||
},
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PerpBalanceLog",
|
||||
"fields": [
|
||||
|
@ -6215,7 +6291,7 @@ export const IDL: MangoV4 = {
|
|||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "computeHealth",
|
||||
"name": "computeAccountData",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
|
@ -6228,17 +6304,7 @@ export const IDL: MangoV4 = {
|
|||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "healthType",
|
||||
"type": {
|
||||
"defined": "HealthType"
|
||||
}
|
||||
}
|
||||
],
|
||||
"returns": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "benchmark",
|
||||
|
@ -6949,6 +7015,66 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "Equity",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "tokens",
|
||||
"type": {
|
||||
"vec": {
|
||||
"defined": "TokenEquity"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "perps",
|
||||
"type": {
|
||||
"vec": {
|
||||
"defined": "PerpEquity"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TokenEquity",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "PerpEquity",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "perpMarketIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "FlashLoanWithdraw",
|
||||
"type": {
|
||||
|
@ -7661,6 +7787,32 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"name": "MangoAccountData",
|
||||
"fields": [
|
||||
{
|
||||
"name": "initHealth",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "maintHealth",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "equity",
|
||||
"type": {
|
||||
"defined": "Equity"
|
||||
},
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PerpBalanceLog",
|
||||
"fields": [
|
||||
|
|
|
@ -225,7 +225,7 @@ async function main() {
|
|||
group,
|
||||
btcDevnetOracle,
|
||||
0,
|
||||
'BTC/USDC',
|
||||
'BTC-PERP',
|
||||
0.1,
|
||||
0,
|
||||
6,
|
||||
|
|
|
@ -49,11 +49,11 @@ async function main() {
|
|||
// deposit and withdraw
|
||||
console.log(`Depositing...50 USDC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`Depositing...0.0005 BTC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
}
|
||||
try {
|
||||
const sig = await client.marginTrade({
|
||||
|
|
|
@ -82,7 +82,7 @@ async function main() {
|
|||
}
|
||||
|
||||
// we closed a serum account, this changes the health accounts we are passing in for future ixs
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
// withdraw all tokens
|
||||
for (const token of mangoAccount.tokensActive()) {
|
||||
|
@ -109,7 +109,7 @@ async function main() {
|
|||
}
|
||||
|
||||
// reload and print current positions
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
console.log(`...mangoAccount ${mangoAccount.publicKey}`);
|
||||
console.log(mangoAccount.toString());
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from '../accounts/serum3';
|
||||
import { MangoClient } from '../client';
|
||||
import { MANGO_V4_ID } from '../constants';
|
||||
import { toUiDecimals } from '../utils';
|
||||
|
||||
//
|
||||
// An example for users based on high level api i.e. the client
|
||||
|
@ -44,7 +45,7 @@ async function main() {
|
|||
),
|
||||
);
|
||||
const group = await client.getGroupForAdmin(admin.publicKey, 0);
|
||||
console.log(`Found group ${group.publicKey.toBase58()}`);
|
||||
console.log(group.toString());
|
||||
|
||||
// create + fetch account
|
||||
console.log(`Creating mangoaccount...`);
|
||||
|
@ -57,19 +58,28 @@ async function main() {
|
|||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||
console.log(mangoAccount.toString());
|
||||
|
||||
await mangoAccount.reloadAccountData(client, group);
|
||||
|
||||
if (true) {
|
||||
// deposit and withdraw
|
||||
console.log(`Depositing...50 USDC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`Depositing...0.0005 BTC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`Withdrawing...1 USDC`);
|
||||
await client.tokenWithdraw(group, mangoAccount, 'USDC', 1, false);
|
||||
await mangoAccount.reload(client);
|
||||
console.log(`Withdrawing...0.1 ORCA`);
|
||||
await client.tokenWithdraw2(
|
||||
group,
|
||||
mangoAccount,
|
||||
'ORCA',
|
||||
0.1 * Math.pow(10, group.banksMap.get('ORCA').mintDecimals),
|
||||
true,
|
||||
);
|
||||
await mangoAccount.reload(client, group);
|
||||
console.log(mangoAccount.toString());
|
||||
|
||||
// serum3
|
||||
console.log(
|
||||
|
@ -88,7 +98,7 @@ async function main() {
|
|||
Date.now(),
|
||||
10,
|
||||
);
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`Placing serum3 bid way above midprice...`);
|
||||
await client.serum3PlaceOrder(
|
||||
|
@ -104,7 +114,7 @@ async function main() {
|
|||
Date.now(),
|
||||
10,
|
||||
);
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`Placing serum3 ask way below midprice...`);
|
||||
await client.serum3PlaceOrder(
|
||||
|
@ -159,16 +169,50 @@ async function main() {
|
|||
|
||||
'BTC/USDC',
|
||||
);
|
||||
}
|
||||
|
||||
// try {
|
||||
// console.log(`Close OO...`);
|
||||
// await client.serum3CloseOpenOrders(group, mangoAccount, 'BTC/USDC');
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// }
|
||||
|
||||
// console.log(`Close mango account...`);
|
||||
// await client.closeMangoAccount(mangoAccount);
|
||||
if (true) {
|
||||
await mangoAccount.reload(client, group);
|
||||
console.log(
|
||||
'mangoAccount.getEquity() ' +
|
||||
toUiDecimals(mangoAccount.getEquity().toNumber()),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getCollateralValue() ' +
|
||||
toUiDecimals(mangoAccount.getCollateralValue().toNumber()),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getAssetsVal() ' +
|
||||
toUiDecimals(mangoAccount.getAssetsVal().toNumber()),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getLiabsVal() ' +
|
||||
toUiDecimals(mangoAccount.getLiabsVal().toNumber()),
|
||||
);
|
||||
console.log(
|
||||
"mangoAccount.getMaxWithdrawWithBorrowForToken(group, 'SOL') " +
|
||||
toUiDecimals(
|
||||
(
|
||||
await mangoAccount.getMaxWithdrawWithBorrowForToken(group, 'SOL')
|
||||
).toNumber(),
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
"mangoAccount.getSerum3MarketMarginAvailable(group, 'BTC/USDC') " +
|
||||
toUiDecimals(
|
||||
mangoAccount
|
||||
.getSerum3MarketMarginAvailable(group, 'BTC/USDC')
|
||||
.toNumber(),
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
"mangoAccount.getPerpMarketMarginAvailable(group, 'BTC-PERP') " +
|
||||
toUiDecimals(
|
||||
mangoAccount
|
||||
.getPerpMarketMarginAvailable(group, 'BTC-PERP')
|
||||
.toNumber(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (true) {
|
||||
|
@ -178,7 +222,7 @@ async function main() {
|
|||
await client.perpPlaceOrder(
|
||||
group,
|
||||
mangoAccount,
|
||||
'BTC/USDC',
|
||||
'BTC-PERP',
|
||||
Side.bid,
|
||||
30000,
|
||||
0.000001,
|
||||
|
@ -196,7 +240,7 @@ async function main() {
|
|||
await client.perpPlaceOrder(
|
||||
group,
|
||||
mangoAccount,
|
||||
'BTC/USDC',
|
||||
'BTC-PERP',
|
||||
Side.ask,
|
||||
30000,
|
||||
0.000001,
|
||||
|
@ -212,7 +256,7 @@ async function main() {
|
|||
console.log(
|
||||
`Waiting for self trade to consume (note: make sure keeper crank is running)...`,
|
||||
);
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
console.log(mangoAccount.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ async function main() {
|
|||
}
|
||||
|
||||
// we closed a serum account, this changes the health accounts we are passing in for future ixs
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
// withdraw all tokens
|
||||
for (const token of mangoAccount.tokensActive()) {
|
||||
|
@ -102,7 +102,7 @@ async function main() {
|
|||
);
|
||||
}
|
||||
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
console.log(`...mangoAccount ${mangoAccount.publicKey}`);
|
||||
console.log(mangoAccount.toString());
|
||||
|
||||
|
|
|
@ -265,7 +265,7 @@ async function main() {
|
|||
group.reloadBanks(client);
|
||||
console.log(`end btc bank ${group.banksMap.get('BTC').toString()}`);
|
||||
|
||||
await mangoAccount.reload(client);
|
||||
await mangoAccount.reload(client, group);
|
||||
console.log(`end balance \n${mangoAccount.toString(group)}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ async function main() {
|
|||
let token = 'BTC';
|
||||
console.log(`Depositing...${amount} 'BTC'`);
|
||||
await user1Client.tokenDeposit(group, user1MangoAccount, token, amount);
|
||||
await user1MangoAccount.reload(user1Client);
|
||||
await user1MangoAccount.reload(user1Client, group);
|
||||
console.log(`${user1MangoAccount.toString(group)}`);
|
||||
|
||||
// user 2
|
||||
|
@ -77,7 +77,7 @@ async function main() {
|
|||
/// user2 deposits some collateral and borrows BTC
|
||||
console.log(`Depositing...${300} 'USDC'`);
|
||||
await user2Client.tokenDeposit(group, user2MangoAccount, 'USDC', 300);
|
||||
await user2MangoAccount.reload(user2Client);
|
||||
await user2MangoAccount.reload(user2Client, group);
|
||||
console.log(`${user2MangoAccount.toString(group)}`);
|
||||
amount = amount / 10;
|
||||
while (true) {
|
||||
|
@ -95,7 +95,7 @@ async function main() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
await user2MangoAccount.reload(user2Client);
|
||||
await user2MangoAccount.reload(user2Client, group);
|
||||
console.log(`${user2MangoAccount.toString(group)}`);
|
||||
|
||||
/// Reduce usdc price
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
} from '@solana/spl-token';
|
||||
import { AccountMeta, PublicKey } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import { QUOTE_DECIMALS } from './accounts/bank';
|
||||
import { I80F48 } from './accounts/I80F48';
|
||||
|
||||
export const I64_MAX_BN = new BN('9223372036854775807').toTwos(64);
|
||||
|
@ -72,7 +73,11 @@ export function toNativeDecimals(amount: number, decimals: number): BN {
|
|||
return new BN(Math.trunc(amount * Math.pow(10, decimals)));
|
||||
}
|
||||
|
||||
export function toUiDecimals(amount: number, decimals: number): number {
|
||||
export function toUiDecimals(
|
||||
amount: I80F48 | number,
|
||||
decimals = QUOTE_DECIMALS,
|
||||
): number {
|
||||
amount = amount instanceof I80F48 ? amount.toNumber() : amount;
|
||||
return amount / Math.pow(10, decimals);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue