Merge remote-tracking branch 'origin/serge/boost-liquidator' into finn/updateDockerBoostFiles

This commit is contained in:
Riordan Panayides 2024-03-12 22:40:10 +00:00
commit 615b5f3f26
66 changed files with 1116 additions and 334 deletions

View File

@ -33,7 +33,7 @@ env:
CARGO_TERM_COLOR: always
SOLANA_VERSION: '1.16.14'
RUST_TOOLCHAIN: '1.69.0'
LOG_PROGRAM: '4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg'
LOG_PROGRAM: 'zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25'
jobs:
format:

View File

@ -2,10 +2,10 @@
exclude = ["programs/margin-trade"]
[programs.localnet]
mango_v4 = "4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg"
mango_v4 = "zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25"
[programs.devnet]
mango_v4 = "4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg"
mango_v4 = "zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25"
[features]
seeds = true

View File

@ -4,13 +4,75 @@ Update this for each program release and mainnet deployment.
## not on mainnet
### v0.23.0, 2024-3-
- Allow disabling asset liquidations for tokens (#867)
This allows listing tokens that have no reliable oracle. Those tokens could be
traded through mango but can't be borrowed, can't have asset weight and can't
even be liquidated.
- Add configurable collateral fees for tokens (#868, #880, #894)
Collateral fees allow the DAO to regularly charge users for using particular
types of collateral to back their liabilities.
- Add force_withdraw token state (#884)
There already is a force_close_borrows state, but for a full delisting user
deposits need to be removed too. In force_withdraw, user deposits can be
permissionlessly withdrawn to their owners' token accounts.
- Flash loan: Add a "swap without flash loan fees" option (#882)
- Cleanup, tests and minor (#878, #875, #854, #838, #895)
## mainnet
### v0.21.2, 2024-1-
### v0.22.0, 2024-3-3
Deployment: Mar 3, 2024 at 23:52:08 Central European Standard Time, https://explorer.solana.com/tx/3MpEMU12Pv7RpSnwfShoM9sbyr41KAEeJFCVx9ypkq8nuK8Q5vm7CRLkdhH3u91yQ4k44a32armZHaoYguX6NqsY
- Perp: Allow reusing your own perp order slots immediately (#817)
Previously users who placed a lot of perp orders and used time-in-force needed
to wait for out-event cranking if their perp order before reusing an order
slot. Now perp order slots can be reused even when the out-event is still on
the event queue.
- Introduce fallback oracles (#790, #813)
Fallback oracles can be used when the primary oracle is stale or not confident.
These oracles need to configured by the DAO to be usable by clients.
Fallback oracles may be based on Orca in addition to the other supported types.
- Add serum3_cancel_by_client_order_id instruction (#798)
Can now cancel by client order id and not just the order id.
- Add configurable platform liquidation fees for tokens and perps (#849, #858)
- Delegates can now withdraw small token amounts to the owner's ata (#820)
- Custom allocator to allow larger heap use if needed (#801)
- Optimize compute use in token_deposit instruction (#786)
- Disable support for v1 and v2 mango accounts (#783)
- Cleanups, logging and tests (#819, #799, #818, #823, #834, #828, #833)
### v0.21.3, 2024-2-9
Deployment: Feb 9, 2024 at 11:21:58 Central European Standard Time, https://explorer.solana.com/tx/44f2wcLyLiic1aycdaPTdfwXJBMeGeuA984kvCByg4L5iGprH6xW3D35gd3bvZ6kU3SipEtoY3kDuexJghbxL89T
- Remove deposit limit check on Openbook v1 when placing an order to sell
deposits (#869)
### v0.21.2, 2024-1-30
Deployment: Jan 30, 2024 at 12:36:09 Central European Standard Time, https://explorer.solana.com/tx/2kw6XhRUpLbh1fsPyQimCgNWjhy717qnUvxNMtLcBS4VNu8i59AJK4wY7wfZV62gT3GkSRTyaDNyD7Dkrg2gUFxC
- Allow fast-listing of Openbook v1 markets (#839, #841)
### v0.21.1, 2024-1-
### v0.21.1, 2024-1-3
Deployment: Jan 3, 2024 at 14:35:10 Central European Standard Time, https://explorer.solana.com/tx/345NMQAvvtXeuGENz8icErXjGNmgkdU84JpvAMJFWXEGYZ2BNxFFcyZsHp5ELwLNUzY4s2hLa6wxHWPBFsTBLspA
- Prevent withdraw operations from bringing token utilization over 100%.
- Prevent extreme interest rates for tokens with borrows but near zero deposits.

2
Cargo.lock generated
View File

@ -3367,7 +3367,7 @@ dependencies = [
[[package]]
name = "mango-v4"
version = "0.22.0"
version = "0.23.0"
dependencies = [
"anchor-lang",
"anchor-spl",

View File

@ -33,9 +33,9 @@ See DEVELOPING.md and FAQ-DEV.md
### Deployments
- devnet: 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg
- mainnet-beta: 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg
- primary mango group on mainnet-beta: 78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX
- devnet: zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25
- mainnet-beta: zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25
- primary mango group on mainnet-beta: AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf
### Release

View File

@ -4,7 +4,7 @@
- $KEY as a path to a keypair (needs around 20 SOL for the buffer)
- $RPC_URL as a url to an RPC node
- 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg is the address of the Mango v4 Program
- zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25 is the address of the Mango v4 Program
- FP4PxqHTVzeG2c6eZd7974F9WvKUSdBeduUK3rjYyvBw is the address of the Mango v4 Program Governance
- Check out the latest version of the `dev` branch
@ -47,17 +47,18 @@
- Create IDL buffer
anchor idl write-buffer --provider.cluster $RPC_URL --provider.wallet $KEY --filepath target/idl/mango_v4_no_docs.json 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg
anchor idl write-buffer --provider.cluster $RPC_URL --provider.wallet $KEY --filepath target/idl/mango_v4_no_docs.json zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25
Save the returned address as $IDL_BUFFER
- Set IDL buffer authority
anchor idl set-authority --provider.cluster $RPC_URL --provider.wallet $KEY --program-id 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg --new-authority FP4PxqHTVzeG2c6eZd7974F9WvKUSdBeduUK3rjYyvBw $IDL_BUFFER
anchor idl set-authority --provider.cluster $RPC_URL --provider.wallet $KEY --program-id zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25 --new-authority FP4PxqHTVzeG2c6eZd7974F9WvKUSdBeduUK3rjYyvBw $IDL_BUFFER
- Make a gist for the proposal description, ideally based on previous upgrade proposals
- Go to the DAO proposal website and make a proposal:
- Upgrade program with the new buffer, set the spill address to the address of $KEY
- Upgrade idl with the new buffer

View File

@ -29,7 +29,7 @@ enum MINTS {
BTC = 'BTC',
}
const NUM_USERS = 4;
const PROGRAM_ID = '4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg';
const PROGRAM_ID = 'zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25';
interface TestUser {
keypair: anchor.web3.Keypair;

View File

@ -552,7 +552,12 @@ async fn send_batched_log_errors_no_confirm(
current_batch.append(ixs.clone());
tx_builder.instructions = current_batch.clone().to_instructions();
if !tx_builder.transaction_size().is_ok() || current_batch.cu > max_cu {
if tx_builder
.transaction_size()
.map(|ts| !ts.is_within_limit())
.unwrap_or(true)
|| current_batch.cu > max_cu
{
tx_builder.instructions = previous_batch.to_instructions();
match tx_builder.send(client).await {
Err(err) => error!("could not send transaction: {err:?}"),

View File

@ -92,7 +92,7 @@ impl<'a> LiquidateHelper<'a> {
let exceeds_cu_limit = new_ixs.cu > self.config.max_cu_per_transaction;
let exceeds_size_limit = {
tx_builder.instructions = new_ixs.clone().to_instructions();
!tx_builder.transaction_size()?.is_ok()
!tx_builder.transaction_size()?.is_within_limit()
};
if exceeds_cu_limit || exceeds_size_limit {
break;

View File

@ -114,16 +114,7 @@ impl Rebalancer {
in_amount_quote: u64,
) -> anyhow::Result<(Signature, jupiter::Quote)> {
let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX);
let sol_token = self.mango_client.context.token(
*self
.mango_client
.context
.token_indexes_by_name
.get("SOL") // TODO: better use mint
.unwrap(),
);
let quote_mint = quote_token.mint;
let sol_mint = sol_token.mint;
let jupiter_version = self.config.jupiter_version;
let full_route_job = self.jupiter_quote(
@ -142,16 +133,8 @@ impl Rebalancer {
);
// For the SOL -> output route we need to adjust the in amount by the SOL price
let sol_price = self
.account_fetcher
.fetch_bank_price(&sol_token.first_bank())?;
let in_amount_sol = (I80F48::from(in_amount_quote) / sol_price)
.ceil()
.to_num::<u64>();
let direct_sol_route_job =
self.jupiter_quote(sol_mint, output_mint, in_amount_sol, true, jupiter_version);
let jobs = vec![full_route_job, direct_quote_route_job, direct_sol_route_job];
let jobs = vec![full_route_job, direct_quote_route_job];
let mut results = futures::future::join_all(jobs).await;
let full_route = results.remove(0)?;
@ -181,26 +164,15 @@ impl Rebalancer {
in_amount: u64,
) -> anyhow::Result<(Signature, jupiter::Quote)> {
let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX);
let sol_token = self.mango_client.context.token(
*self
.mango_client
.context
.token_indexes_by_name
.get("SOL") // TODO: better use mint
.unwrap(),
);
let quote_mint = quote_token.mint;
let sol_mint = sol_token.mint;
let jupiter_version = self.config.jupiter_version;
let full_route_job =
self.jupiter_quote(input_mint, quote_mint, in_amount, false, jupiter_version);
let direct_quote_route_job =
self.jupiter_quote(input_mint, quote_mint, in_amount, true, jupiter_version);
let direct_sol_route_job =
self.jupiter_quote(input_mint, sol_mint, in_amount, true, jupiter_version);
let jobs = vec![full_route_job, direct_quote_route_job, direct_sol_route_job];
let jobs = vec![full_route_job, direct_quote_route_job];
let mut results = futures::future::join_all(jobs).await;
let full_route = results.remove(0)?;
@ -231,7 +203,7 @@ impl Rebalancer {
.prepare_swap_transaction(full)
.await?;
let tx_size = builder.transaction_size()?;
if tx_size.is_ok() {
if tx_size.is_within_limit() {
return Ok((builder, full.clone()));
}
trace!(

View File

@ -2233,7 +2233,7 @@ pub struct TransactionSize {
}
impl TransactionSize {
pub fn is_ok(&self) -> bool {
pub fn is_within_limit(&self) -> bool {
let limit = Self::limit();
self.length <= limit.length && self.accounts <= limit.accounts
}

View File

@ -144,7 +144,7 @@ impl Default for ComputeEstimates {
// the base cost is mostly the division
cu_per_charge_collateral_fees: 20_000,
// per-chargable-token cost
cu_per_charge_collateral_fees_token: 12_000,
cu_per_charge_collateral_fees_token: 15_000,
}
}
}

View File

@ -1,5 +1,5 @@
{
"version": "0.22.0",
"version": "0.23.0",
"name": "mango_v4",
"instructions": [
{
@ -1760,6 +1760,36 @@
}
]
},
{
"name": "sequenceCheck",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false,
"relations": [
"group",
"owner"
]
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "expectedSequenceNumber",
"type": "u8"
}
]
},
{
"name": "stubOracleCreate",
"accounts": [
@ -7871,13 +7901,8 @@
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
1
]
}
"name": "sequenceNumber",
"type": "u8"
},
{
"name": "netDeposits",
@ -9669,13 +9694,8 @@
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
1
]
}
"name": "sequenceNumber",
"type": "u8"
},
{
"name": "netDeposits",
@ -11008,6 +11028,9 @@
},
{
"name": "TokenForceWithdraw"
},
{
"name": "SequenceCheck"
}
]
}
@ -12871,6 +12894,71 @@
}
]
},
{
"name": "PerpLiqBaseOrPositivePnlLogV3",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "perpMarketIndex",
"type": "u16",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "baseTransferLiqee",
"type": "i64",
"index": false
},
{
"name": "quoteTransferLiqee",
"type": "i128",
"index": false
},
{
"name": "quoteTransferLiqor",
"type": "i128",
"index": false
},
{
"name": "quotePlatformFee",
"type": "i128",
"index": false
},
{
"name": "pnlTransfer",
"type": "i128",
"index": false
},
{
"name": "pnlSettleLimitTransferRecurring",
"type": "i64",
"index": false
},
{
"name": "pnlSettleLimitTransferOneshot",
"type": "i64",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
}
]
},
{
"name": "PerpLiqBankruptcyLog",
"fields": [
@ -13888,6 +13976,46 @@
"name": "fee",
"type": "i128",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
}
]
},
{
"name": "ForceWithdrawLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "quantity",
"type": "u64",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
},
{
"name": "toTokenAccount",
"type": "publicKey",
"index": false
}
]
}
@ -14242,6 +14370,16 @@
"code": 6069,
"name": "TokenAssetLiquidationDisabled",
"msg": "the asset does not allow liquidation"
},
{
"code": 6070,
"name": "BorrowsRequireHealthAccountBank",
"msg": "for borrows the bank must be in the health account list"
},
{
"code": 6071,
"name": "InvalidSequenceNumber",
"msg": "invalid sequence number"
}
]
}

View File

@ -1,6 +1,6 @@
{
"name": "@blockworks-foundation/mango-v4",
"version": "0.21.29",
"version": "0.23.0-rc2",
"description": "Typescript Client for mango-v4 program.",
"repository": "https://github.com/blockworks-foundation/mango-v4",
"author": {
@ -62,7 +62,7 @@
"trailingComma": "all"
},
"dependencies": {
"@blockworks-foundation/mango-v4-settings": "0.4.10",
"@blockworks-foundation/mango-v4-settings": "0.14.15",
"@blockworks-foundation/mangolana": "0.0.14",
"@coral-xyz/anchor": "^0.28.1-beta.2",
"@project-serum/serum": "0.13.65",

View File

@ -1,6 +1,6 @@
[package]
name = "mango-v4"
version = "0.22.0"
version = "0.23.0"
description = "Created with Anchor"
edition = "2021"

View File

@ -45,6 +45,7 @@ pub use perp_place_order::*;
pub use perp_settle_fees::*;
pub use perp_settle_pnl::*;
pub use perp_update_funding::*;
pub use sequence_check::*;
pub use serum3_cancel_all_orders::*;
pub use serum3_cancel_order::*;
pub use serum3_close_open_orders::*;
@ -123,6 +124,7 @@ mod perp_place_order;
mod perp_settle_fees;
mod perp_settle_pnl;
mod perp_update_funding;
mod sequence_check;
mod serum3_cancel_all_orders;
mod serum3_cancel_order;
mod serum3_close_open_orders;

View File

@ -0,0 +1,20 @@
use crate::error::*;
use crate::state::*;
use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct SequenceCheck<'info> {
#[account(
constraint = group.load()?.is_ix_enabled(IxGate::SequenceCheck) @ MangoError::IxIsDisabled,
)]
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen
)]
pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>,
}

View File

@ -145,6 +145,10 @@ pub enum MangoError {
MissingFeedForCLMMOracle,
#[msg("the asset does not allow liquidation")]
TokenAssetLiquidationDisabled,
#[msg("for borrows the bank must be in the health account list")]
BorrowsRequireHealthAccountBank,
#[msg("invalid sequence number")]
InvalidSequenceNumber,
}
impl MangoError {

View File

@ -96,6 +96,7 @@ pub fn ix_gate_set(ctx: Context<IxGateSet>, ix_gate: u128) -> Result<()> {
);
log_if_changed(&group, ix_gate, IxGate::Serum3PlaceOrderV2);
log_if_changed(&group, ix_gate, IxGate::TokenForceWithdraw);
log_if_changed(&group, ix_gate, IxGate::SequenceCheck);
group.ix_gate = ix_gate;

View File

@ -35,6 +35,7 @@ pub use perp_place_order::*;
pub use perp_settle_fees::*;
pub use perp_settle_pnl::*;
pub use perp_update_funding::*;
pub use sequence_check::*;
pub use serum3_cancel_all_orders::*;
pub use serum3_cancel_order::*;
pub use serum3_cancel_order_by_client_order_id::*;
@ -104,6 +105,7 @@ mod perp_place_order;
mod perp_settle_fees;
mod perp_settle_pnl;
mod perp_update_funding;
mod sequence_check;
mod serum3_cancel_all_orders;
mod serum3_cancel_order;
mod serum3_cancel_order_by_client_order_id;

View File

@ -0,0 +1,18 @@
use anchor_lang::prelude::*;
use crate::accounts_ix::*;
use crate::error::MangoError;
use crate::state::*;
pub fn sequence_check(ctx: Context<SequenceCheck>, expected_sequence_number: u8) -> Result<()> {
let mut account = ctx.accounts.account.load_full_mut()?;
require_eq!(
expected_sequence_number,
account.fixed.sequence_number,
MangoError::InvalidSequenceNumber
);
account.fixed.sequence_number = account.fixed.sequence_number.wrapping_add(1);
Ok(())
}

View File

@ -5,7 +5,7 @@ use anchor_lang::prelude::*;
use fixed::types::I80F48;
use crate::accounts_ix::*;
use crate::logs::{emit_stack, TokenCollateralFeeLog};
use crate::logs::{emit_stack, TokenBalanceLog, TokenCollateralFeeLog};
pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) -> Result<()> {
let group = ctx.accounts.group.load()?;
@ -103,12 +103,25 @@ pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) ->
bank.collected_fees_native += fee;
bank.collected_collateral_fees += fee;
let token_info = health_cache.token_info(bank.token_index)?;
let token_position = account.token_position(bank.token_index)?;
emit_stack(TokenCollateralFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: bank.token_index,
fee: fee.to_bits(),
asset_usage_fraction: asset_usage_scaling.to_bits(),
price: token_info.prices.oracle.to_bits(),
});
emit_stack(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: bank.token_index,
indexed_position: token_position.indexed_position.to_bits(),
deposit_index: bank.deposit_index.to_bits(),
borrow_index: bank.borrow_index.to_bits(),
})
}

View File

@ -39,7 +39,7 @@ use state::{
TokenIndex, TCS_START_INCENTIVE,
};
declare_id!("4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg");
declare_id!("zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25");
#[program]
pub mod mango_v4 {
@ -458,6 +458,12 @@ pub mod mango_v4 {
Ok(())
}
pub fn sequence_check(ctx: Context<SequenceCheck>, expected_sequence_number: u8) -> Result<()> {
#[cfg(feature = "enable-gpl")]
instructions::sequence_check(ctx, expected_sequence_number)?;
Ok(())
}
// todo:
// ckamm: generally, using an I80F48 arg will make it harder to call
// because generic anchor clients won't know how to deal with it

View File

@ -803,6 +803,7 @@ pub struct TokenCollateralFeeLog {
pub token_index: u16,
pub asset_usage_fraction: i128,
pub fee: i128,
pub price: i128,
}
#[event]

View File

@ -394,14 +394,18 @@ impl Bank {
require_gte!(self.rate0, I80F48::ZERO);
require_gte!(self.rate1, I80F48::ZERO);
require_gte!(self.max_rate, I80F48::ZERO);
require_gte!(self.adjustment_factor, 0.0);
require_gte!(self.loan_fee_rate, 0.0);
require_gte!(self.loan_origination_fee_rate, 0.0);
require_gte!(self.maint_asset_weight, 0.0);
require_gte!(self.stable_price_model.delay_growth_limit, 0.0);
require_gte!(self.stable_price_model.stable_growth_limit, 0.0);
require_gte!(self.init_asset_weight, 0.0);
require_gte!(self.maint_asset_weight, self.init_asset_weight);
require_gte!(self.maint_liab_weight, 0.0);
require_gte!(self.init_liab_weight, 0.0);
require_gte!(self.init_liab_weight, self.maint_liab_weight);
require_gte!(self.liquidation_fee, 0.0);
require_gte!(self.min_vault_to_deposits_ratio, 0.0);
require_gte!(1.0, self.min_vault_to_deposits_ratio);
require_gte!(self.net_borrow_limit_per_window_quote, -1);
require_gt!(self.borrow_weight_scale_start_quote, 0.0);
require_gt!(self.deposit_weight_scale_start_quote, 0.0);
@ -411,6 +415,7 @@ impl Bank {
require_gte!(self.flash_loan_swap_fee_rate, 0.0);
require_gte!(self.interest_curve_scaling, 1.0);
require_gte!(self.interest_target_utilization, 0.0);
require_gte!(1.0, self.interest_target_utilization);
require_gte!(self.maint_weight_shift_duration_inv, 0.0);
require_gte!(self.maint_weight_shift_asset_target, 0.0);
require_gte!(self.maint_weight_shift_liab_target, 0.0);

View File

@ -246,6 +246,7 @@ pub enum IxGate {
TokenConditionalSwapCreateLinearAuction = 70,
Serum3PlaceOrderV2 = 71,
TokenForceWithdraw = 72,
SequenceCheck = 73,
// NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction.
}

View File

@ -123,8 +123,7 @@ pub struct MangoAccount {
pub bump: u8,
#[derivative(Debug = "ignore")]
pub padding: [u8; 1],
pub sequence_number: u8,
// (Display only)
// Cumulative (deposits - withdraws)
@ -200,7 +199,7 @@ impl MangoAccount {
in_health_region: 0,
account_num: 0,
bump: 0,
padding: Default::default(),
sequence_number: 0,
net_deposits: 0,
perp_spot_transfers: 0,
health_region_begin_init_health: 0,
@ -325,7 +324,7 @@ pub struct MangoAccountFixed {
being_liquidated: u8,
in_health_region: u8,
pub bump: u8,
pub padding: [u8; 1],
pub sequence_number: u8,
pub net_deposits: i64,
pub perp_spot_transfers: i64,
pub health_region_begin_init_health: i64,
@ -2897,7 +2896,7 @@ mod tests {
being_liquidated: fixed.being_liquidated,
in_health_region: fixed.in_health_region,
bump: fixed.bump,
padding: Default::default(),
sequence_number: 0,
net_deposits: fixed.net_deposits,
perp_spot_transfers: fixed.perp_spot_transfers,
health_region_begin_init_health: fixed.health_region_begin_init_health,

View File

@ -462,6 +462,8 @@ async fn test_bank_maint_weight_shift() -> Result<(), TransportError> {
mint: mints[0].pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
init_asset_weight_opt: Some(0.0),
init_liab_weight_opt: Some(2.0),
maint_weight_shift_start_opt: Some(start_time + 1000),
maint_weight_shift_end_opt: Some(start_time + 2000),
maint_weight_shift_asset_target_opt: Some(0.5),
@ -685,3 +687,105 @@ async fn test_bank_deposit_limit() -> Result<(), TransportError> {
Ok(())
}
#[tokio::test]
async fn test_sequence_check() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..1];
let mango_setup::GroupWithTokens { group, .. } = mango_setup::GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..mango_setup::GroupWithTokensConfig::default()
}
.create(solana)
.await;
let account = send_tx(
solana,
AccountCreateInstruction {
account_num: 0,
token_count: 6,
serum3_count: 3,
perp_count: 3,
perp_oo_count: 3,
token_conditional_swap_count: 3,
group,
owner,
payer,
},
)
.await
.unwrap()
.account;
let mango_account = get_mango_account(solana, account).await;
assert_eq!(mango_account.fixed.sequence_number, 0);
//
// TEST: Sequence check with right sequence number
//
send_tx(
solana,
SequenceCheckInstruction {
account,
owner,
expected_sequence_number: 0,
},
)
.await
.unwrap();
let mango_account = get_mango_account(solana, account).await;
assert_eq!(mango_account.fixed.sequence_number, 1);
send_tx(
solana,
SequenceCheckInstruction {
account,
owner,
expected_sequence_number: 1,
},
)
.await
.unwrap();
let mango_account = get_mango_account(solana, account).await;
assert_eq!(mango_account.fixed.sequence_number, 2);
//
// TEST: Sequence check with wrong sequence number
//
send_tx_expect_error!(
solana,
SequenceCheckInstruction {
account,
owner,
expected_sequence_number: 1
},
MangoError::InvalidSequenceNumber
);
send_tx_expect_error!(
solana,
SequenceCheckInstruction {
account,
owner,
expected_sequence_number: 4
},
MangoError::InvalidSequenceNumber
);
let mango_account = get_mango_account(solana, account).await;
assert_eq!(mango_account.fixed.sequence_number, 2);
Ok(())
}

View File

@ -501,6 +501,7 @@ async fn test_force_withdraw_token() -> Result<(), TransportError> {
mint: token.mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
init_asset_weight_opt: Some(0.0),
maint_asset_weight_opt: Some(0.0),
reduce_only_opt: Some(1),
disable_asset_liquidation_opt: Some(true),

View File

@ -162,7 +162,7 @@ async fn test_ix_gate_set() -> Result<(), TransportError> {
//
// test cu budget, ix has a lot of logging
// e.g. Program 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg consumed 66986 of 75000 compute units
// e.g. Program zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25 consumed 66986 of 75000 compute units
send_tx(
solana,
IxGateSetInstruction {

View File

@ -5139,3 +5139,42 @@ impl ClientInstruction for TokenChargeCollateralFeesInstruction {
vec![]
}
}
#[derive(Default)]
pub struct SequenceCheckInstruction {
pub account: Pubkey,
pub owner: TestKeypair,
pub expected_sequence_number: u8,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for SequenceCheckInstruction {
type Accounts = mango_v4::accounts::SequenceCheck;
type Instruction = mango_v4::instruction::SequenceCheck;
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 {
expected_sequence_number: self.expected_sequence_number,
};
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let accounts = Self::Accounts {
group: account.fixed.group,
account: self.account,
owner: self.owner.pubkey(),
};
let instruction = make_instruction(program_id, &accounts, &instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
vec![self.owner]
}
}

View File

@ -3,7 +3,7 @@
set -ex pipefail
WALLET_WITH_FUNDS=~/.config/solana/mango-mainnet-1.json
PROGRAM_ID=4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg
PROGRAM_ID=zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25
# build program,
anchor build -- --features enable-gpl

View File

@ -1,7 +1,7 @@
#!/bin/bash
# WALLET_WITH_FUNDS=~/.config/solana/mango-devnet.json
# PROGRAM_ID=4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg
# PROGRAM_ID=zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25
anchor build -- --features enable-gpl
./idl-fixup.sh

View File

@ -5,7 +5,7 @@
"name": "mainnet-beta.clarkeni",
"publicKey": "DLdcpC6AsAJ9xeKMR3WhHrN5sM5o7GVVXQhQ5vwisTtz",
"serum3ProgramId": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
"mangoProgramId": "4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg",
"mangoProgramId": "zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25",
"banks": [
{
"name": "SOL",

View File

@ -73,6 +73,7 @@ async function main(): Promise<void> {
group,
usdcDevnetMint,
usdcDevnetOracle.publicKey,
PublicKey.default,
0, // tokenIndex
'USDC',
{
@ -101,6 +102,7 @@ async function main(): Promise<void> {
group,
solDevnetMint,
solDevnetOracle,
PublicKey.default,
4, // tokenIndex
'SOL',
{
@ -130,6 +132,7 @@ async function main(): Promise<void> {
group,
usdtDevnetMint,
usdcDevnetOracle.publicKey,
PublicKey.default,
5, // tokenIndex
'USDT',
{
@ -163,6 +166,7 @@ async function main(): Promise<void> {
group.getFirstBankByMint(insuranceMint),
0,
'SOL/USDC',
0.5,
);
await group.reloadAll(client);
const serum3Market = group.getSerum3MarketByExternalMarket(
@ -211,6 +215,7 @@ async function main(): Promise<void> {
1.0,
2 * 60 * 60,
0.025,
0,
);
await group.reloadAll(client);
const perpMarket = group.getPerpMarketByMarketIndex(

View File

@ -6,7 +6,7 @@ async function main(): Promise<void> {
const client = MangoClient.connectDefault(process.env.MB_CLUSTER_URL!);
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf'),
);
const mangoAccounts = await client.getAllMangoAccounts(group, true);
const solPerp = group.getPerpMarketByName('SOL-PERP');

View File

@ -34,7 +34,7 @@ async function main(): Promise<void> {
);
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf'),
);
let account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK));
@ -51,24 +51,24 @@ async function main(): Promise<void> {
}),
);
// const sig = await client.tcsTakeProfitOnDeposit(
// group,
// account,
// group.getFirstBankByTokenIndex(4 as TokenIndex),
// group.getFirstBankByTokenIndex(0 as TokenIndex),
// group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice + 1,
// false,
// null,
// null,
// null,
// );
const sig = await client.tcsTakeProfitOnDeposit(
group,
account,
group.getFirstBankByTokenIndex(4 as TokenIndex),
group.getFirstBankByTokenIndex(0 as TokenIndex),
group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice - 1,
false,
null,
null,
null,
);
// const sig = await client.tcsStopLossOnDeposit(
// group,
// account,
// group.getFirstBankByTokenIndex(4 as TokenIndex),
// group.getFirstBankByTokenIndex(0 as TokenIndex),
// group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice - 1,
// group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice - 0.001,
// false,
// null,
// null,
@ -80,7 +80,7 @@ async function main(): Promise<void> {
// account,
// group.getFirstBankByTokenIndex(0 as TokenIndex),
// group.getFirstBankByTokenIndex(4 as TokenIndex),
// group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice - 1,
// group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice - 0.001,
// true,
// null,
// null,
@ -88,18 +88,18 @@ async function main(): Promise<void> {
// null,
// );
const sig = await client.tcsStopLossOnBorrow(
group,
account,
group.getFirstBankByTokenIndex(0 as TokenIndex),
group.getFirstBankByTokenIndex(4 as TokenIndex),
group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice + 1,
true,
null,
null,
null,
null,
);
// const sig = await client.tcsStopLossOnBorrow(
// group,
// account,
// group.getFirstBankByTokenIndex(0 as TokenIndex),
// group.getFirstBankByTokenIndex(4 as TokenIndex),
// group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice + 0.001,
// true,
// null,
// null,
// null,
// null,
// );
console.log(sig);

View File

@ -277,7 +277,7 @@ async function main(): Promise<void> {
});
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf'),
);
const mangoAccounts = await client.getAllMangoAccounts(group, true);

View File

@ -94,6 +94,7 @@ async function main() {
group,
usdcDevnetMint,
usdcDevnetOracle.publicKey,
PublicKey.default,
0, // tokenIndex
'USDC',
{
@ -124,6 +125,7 @@ async function main() {
group,
solDevnetMint,
solDevnetOracle,
PublicKey.default,
1, // tokenIndex
'SOL',
{
@ -218,6 +220,7 @@ async function main() {
1.0,
2 * 60 * 60,
0.025,
0,
);
await group.reloadAll(client);
const perpMarket = group.getPerpMarketByMarketIndex(0 as PerpMarketIndex);

View File

@ -29,7 +29,7 @@ async function addSpotMarket() {
console.log(`Admin ${admin.publicKey.toBase58()}`);
// fetch group
const groupPk = '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
const groupPk = 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
const group = await client.getGroup(new PublicKey(groupPk));
console.log(`Found group ${group.publicKey.toBase58()}`);

View File

@ -206,6 +206,7 @@ async function registerTokens() {
group,
usdcMainnetMint,
usdcMainnetOracle.publicKey,
PublicKey.default,
0,
'USDC',
{
@ -226,6 +227,7 @@ async function registerTokens() {
group,
usdtMainnetMint,
usdtMainnetOracle,
PublicKey.default,
1,
'USDT',
{
@ -246,6 +248,7 @@ async function registerTokens() {
group,
daiMainnetMint,
daiMainnetOracle,
PublicKey.default,
2,
'DAI',
{
@ -266,6 +269,7 @@ async function registerTokens() {
group,
ethMainnetMint,
ethMainnetOracle,
PublicKey.default,
3,
'ETH',
{
@ -286,6 +290,7 @@ async function registerTokens() {
group,
solMainnetMint,
solMainnetOracle,
PublicKey.default,
4,
'SOL',
{
@ -306,6 +311,7 @@ async function registerTokens() {
group,
msolMainnetMint,
msolMainnetOracle,
PublicKey.default,
5,
'MSOL',
{
@ -450,6 +456,7 @@ async function registerPerpMarkets() {
1.0,
2 * 60 * 60,
0.025,
0,
);
await client.perpCreateMarket(
@ -482,6 +489,7 @@ async function registerPerpMarkets() {
1.0,
2 * 60 * 60,
0.2, // 20% positive pnl liquidation fee?
0,
);
}

View File

@ -15,7 +15,7 @@ const SOME_KEYPAIR =
const CLUSTER: Cluster =
(process.env.CLUSTER_OVERRIDE as Cluster) || 'mainnet-beta';
const GROUP_PK = '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
const GROUP_PK = 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
async function main(): Promise<void> {
const options = AnchorProvider.defaultOptions();

View File

@ -34,7 +34,7 @@ async function main() {
console.log(`Admin ${admin.publicKey.toBase58()}`);
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf'),
);
console.log(`${group.toString()}`);

View File

@ -42,7 +42,7 @@ async function x(): Promise<void> {
);
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf'),
);
const { bestRoute } = await fetchRoutes(

View File

@ -23,7 +23,7 @@ import { bpsToDecimal, percentageToDecimal, toNative } from '../src/utils';
const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR } = process.env;
const CLIENT_USER = MB_PAYER_KEYPAIR;
const GROUP_PK = '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
const GROUP_PK = 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
const defaultOracleConfig = {
confFilter: 0.1,
@ -60,31 +60,31 @@ async function buildClient(): Promise<MangoClient> {
);
}
async function groupEdit(): Promise<void> {
const client = await buildClient();
const group = await client.getGroup(new PublicKey(GROUP_PK));
const ix = await client.program.methods
.groupEdit(
null, // admin
null, // fastListingAdmin
null, // securityAdmin
null, // testing
null, // version
null, // depositLimitQuote
null, // feesPayWithMngo
null, // feesMngoBonusRate
null, // feesSwapMangoAccount
6, // feesMngoTokenIndex
null, // feesExpiryInterval
5, // allowedFastListingsPerInterval
)
.accounts({
group: group.publicKey,
admin: group.admin,
})
.instruction();
console.log(serializeInstructionToBase64(ix));
}
// async function groupEdit(): Promise<void> {
// const client = await buildClient();
// const group = await client.getGroup(new PublicKey(GROUP_PK));
// const ix = await client.program.methods
// .groupEdit(
// null, // admin
// null, // fastListingAdmin
// null, // securityAdmin
// null, // testing
// null, // version
// null, // depositLimitQuote
// null, // feesPayWithMngo
// null, // feesMngoBonusRate
// null, // feesSwapMangoAccount
// 6, // feesMngoTokenIndex
// null, // feesExpiryInterval
// 5, // allowedFastListingsPerInterval
// )
// .accounts({
// group: group.publicKey,
// admin: group.admin,
// })
// .instruction();
// console.log(serializeInstructionToBase64(ix));
// }
// async function tokenRegister(): Promise<void> {
// const client = await buildClient();
@ -265,6 +265,7 @@ async function perpCreate(): Promise<void> {
1,
new BN(60 * 60),
percentageToDecimal(10),
0,
)
.accounts({
group: group.publicKey,
@ -358,6 +359,7 @@ async function perpEdit(): Promise<void> {
params.positivePnlLiquidationFee,
params.name,
params.forceClose,
0,
)
.accounts({
group: group.publicKey,
@ -466,7 +468,7 @@ async function idlSetAuthority(): Promise<void> {
async function main(): Promise<void> {
try {
await groupEdit();
// await groupEdit();
// await tokenRegister();
// await tokenEdit();
// await perpCreate();

View File

@ -9,7 +9,7 @@ const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR, MANGO_ACCOUNT, MINT, NATIVE_AMOUNT } =
process.env;
const CLIENT_USER = MB_PAYER_KEYPAIR;
const GROUP_PK = '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
const GROUP_PK = 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
async function buildClient(): Promise<MangoClient> {
const clientKeypair = Keypair.fromSecretKey(

View File

@ -13,7 +13,7 @@ const CLUSTER_URL =
const USER_KEYPAIR =
process.env.USER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR;
const GROUP_PK =
process.env.GROUP_PK || '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
process.env.GROUP_PK || 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
const PERP_MARKET_INDEX = Number(
process.env.PERP_MARKET_INDEX,
) as PerpMarketIndex;

View File

@ -13,7 +13,7 @@ const CLUSTER_URL =
const USER_KEYPAIR =
process.env.USER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR;
const GROUP_PK =
process.env.GROUP_PK || '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
process.env.GROUP_PK || 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
const MARKET_INDEX = Number(process.env.MARKET_INDEX) as MarketIndex;
async function forceCloseSerum3Market(): Promise<void> {

View File

@ -12,7 +12,7 @@ const CLUSTER_URL =
const USER_KEYPAIR =
process.env.USER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR;
const GROUP_PK =
process.env.GROUP_PK || '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
process.env.GROUP_PK || 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
const TOKEN_INDEX = Number(process.env.TOKEN_INDEX) as TokenIndex;
async function forceWithdrawTokens(): Promise<void> {

View File

@ -5,7 +5,7 @@ import { toUiDecimals } from '../src/utils';
async function run() {
const client = await MangoClient.connectDefault(process.env.MB_CLUSTER_URL!);
let group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf'),
);
let accounts = await client.getAllMangoAccounts(group, true);

View File

@ -1,6 +1,6 @@
import { AnchorProvider, BN, Wallet } from '@coral-xyz/anchor';
import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js';
import * as splToken from '@solana/spl-token';
import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { Bank } from '../../src/accounts/bank';
import {
@ -280,6 +280,7 @@ async function main() {
group,
newMint,
newOracle.publicKey,
PublicKey.default,
newTokenIndex,
'TMP',
{

View File

@ -143,6 +143,7 @@ async function registerTokens(): Promise<void> {
group,
usdcMainnetMint,
usdcMainnetOracle,
PublicKey.default,
0,
'USDC',
defaultTokenParams,

View File

@ -6,7 +6,7 @@ import { MANGO_V4_ID } from '../src/constants';
const { MB_CLUSTER_URL } = process.env;
const GROUP_PK = '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
const GROUP_PK = 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
async function buildClient(): Promise<MangoClient> {
const clientKeypair = new Keypair();

View File

@ -14,7 +14,7 @@ dotenv.config();
const CLUSTER_URL =
process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL;
const GROUP_PK =
process.env.GROUP_PK || '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
process.env.GROUP_PK || 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
const CLUSTER: Cluster =
(process.env.CLUSTER_OVERRIDE as Cluster) || 'mainnet-beta';

View File

@ -9,7 +9,7 @@ dotenv.config();
const CLUSTER_URL =
process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL;
const GROUP_PK =
process.env.GROUP_PK || '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
process.env.GROUP_PK || 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
const CLUSTER: Cluster =
(process.env.CLUSTER_OVERRIDE as Cluster) || 'mainnet-beta';

View File

@ -6,7 +6,7 @@ import { getLiquidationBatches, getRiskStats } from '../src/risk';
const { MB_CLUSTER_URL } = process.env;
const GROUP_PK = '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
const GROUP_PK = 'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf';
async function buildClient(): Promise<MangoClient> {
const clientKeypair = new Keypair();

View File

@ -76,7 +76,7 @@ async function main(): Promise<void> {
});
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf'),
);
let mangoAccounts = await client.getAllMangoAccounts(group, true);

View File

@ -27,7 +27,7 @@ function getBankForOracle(group: Group, oracle: PublicKey): Bank | PerpMarket {
async function main(): Promise<void> {
const client = await MangoClient.connectDefault(process.env.MB_CLUSTER_URL!);
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
new PublicKey('AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf'),
);
const oracles1 = Array.from(group.banksMapByName.values()).map(

View File

@ -1,4 +1,5 @@
import {
LISTING_PRESETS,
MidPriceImpact,
getMidPriceImpacts,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools';
@ -9,6 +10,7 @@ import {
getTokenOwnerRecord,
getTokenOwnerRecordAddress,
} from '@solana/spl-governance';
import { Builder } from '../src/builder';
import {
AccountMeta,
@ -22,10 +24,10 @@ import fs from 'fs';
import { Bank } from '../src/accounts/bank';
import { Group } from '../src/accounts/group';
import { MangoAccount } from '../src/accounts/mangoAccount';
import { Builder } from '../src/builder';
import { MangoClient } from '../src/client';
import { NullTokenEditParams } from '../src/clientIxParamBuilder';
import { MANGO_V4_MAIN_GROUP as MANGO_V4_PRIMARY_GROUP } from '../src/constants';
import { I80F48 } from '../src/numbers/I80F48';
import {
LiqorPriceImpact,
buildGroupGrid,
@ -155,30 +157,69 @@ async function updateTokenParams(): Promise<void> {
const midPriceImpacts = getMidPriceImpacts(group.pis);
const pisForLiqor: LiqorPriceImpact[][] = await buildGroupGrid(
group,
allMangoAccounts,
stepSize,
);
const pisForLiqor: LiqorPriceImpact[][] = [];
// eslint-disable-next-line no-constant-condition
if (false) {
const pisForLiqor: LiqorPriceImpact[][] = await buildGroupGrid(
group,
allMangoAccounts,
stepSize,
);
}
// Deposit limits header
console.log(
`${'name'.padStart(20)} ${'maxLiqBatchUi'.padStart(
15,
)} ${'maxLiqBatchUi'.padStart(15)} ${'sellImpact'.padStart(
12,
)}$ ${'pi %'.padStart(12)}% ${'aNDUi'.padStart(20)}${'aNDQuoteUi'.padStart(
20,
)} ${'uiDeposits'.padStart(12)} ${'uiDeposits'.padStart(
12,
)} ${'depositLimitsUi'.padStart(12)}`,
);
// eslint-disable-next-line no-constant-condition
if (false) {
// Deposit limits header
console.log(
`${'name'.padStart(20)} ${'maxLiqBatchUi'.padStart(
15,
)} ${'maxLiqBatchUi'.padStart(15)} ${'sellImpact'.padStart(
12,
)}$ ${'pi %'.padStart(12)}% ${'aNDUi'.padStart(
20,
)}${'aNDQuoteUi'.padStart(20)} ${'uiDeposits'.padStart(
12,
)} ${'uiDeposits'.padStart(12)} ${'depositLimitsUi'.padStart(12)}`,
);
}
Array.from(group.banksMapByTokenIndex.values())
.map((banks) => banks[0])
// .filter((bank) => bank.name == 'MSOL')
.sort((a, b) => a.name.localeCompare(b.name))
.forEach(async (bank) => {
const builder = Builder(NullTokenEditParams);
let change = false;
// eslint-disable-next-line no-constant-condition
if (true) {
const tier = Object.values(LISTING_PRESETS).find((x) =>
x.initLiabWeight.toFixed(1) === '1.8'
? x.initLiabWeight.toFixed(1) ===
bank?.initLiabWeight.toNumber().toFixed(1) &&
x.reduceOnly === bank.reduceOnly
: x.initLiabWeight.toFixed(1) ===
bank?.initLiabWeight.toNumber().toFixed(1),
);
if (!tier) {
console.log(`${bank.name}, no tier found`);
} else {
console.log(
`${bank.name.padStart(10)}, ${bank.loanFeeRate
.mul(I80F48.fromNumber(100))
.toFixed(2)}, ${bank.loanOriginationFeeRate
.mul(I80F48.fromNumber(100))
.toFixed(2)}, ${tier?.preset_name.padStart(5)}, ${(
tier.loanFeeRate * 100
).toFixed(2)}, ${(tier!.loanOriginationFeeRate * 100).toFixed(2)}`,
);
builder.loanFeeRate(tier!.loanFeeRate);
builder.loanOriginationFeeRate(tier!.loanOriginationFeeRate);
builder.flashLoanSwapFeeRate(tier!.loanOriginationFeeRate);
change = true;
}
}
try {
// formulas are sourced from here
// https://www.notion.so/mango-markets/Mango-v4-Risk-parameter-recommendations-d309cdf5faac4aeea7560356e68532ab
@ -189,115 +230,104 @@ async function updateTokenParams(): Promise<void> {
// 4 * priceImpact.target_amount,
// );
const builder = Builder(NullTokenEditParams);
// Net borrow limits
if (!bank.areBorrowsReduceOnly()) {
const netBorrowLimitPerWindowQuote = Math.max(
10_000,
Math.min(bank.uiDeposits() * bank.uiPrice, 300_000) / 3 +
Math.max(0, bank.uiDeposits() * bank.uiPrice - 300_000) / 5,
);
builder.netBorrowLimitPerWindowQuote(
toNativeI80F48ForQuote(netBorrowLimitPerWindowQuote).toNumber(),
);
change = true;
if (
netBorrowLimitPerWindowQuote !=
toUiDecimalsForQuote(bank.netBorrowLimitPerWindowQuote)
) {
console.log(
`${
bank.name
} new - ${netBorrowLimitPerWindowQuote.toLocaleString()}, old - ${toUiDecimalsForQuote(
bank.netBorrowLimitPerWindowQuote,
).toLocaleString()}`,
// eslint-disable-next-line no-constant-condition
if (false) {
// Net borrow limits
const netBorrowLimitPerWindowQuote = Math.max(
10_000,
Math.min(bank.uiDeposits() * bank.uiPrice, 300_000) / 3 +
Math.max(0, bank.uiDeposits() * bank.uiPrice - 300_000) / 5,
);
builder.netBorrowLimitPerWindowQuote(
toNativeI80F48ForQuote(netBorrowLimitPerWindowQuote).toNumber(),
);
change = true;
if (
netBorrowLimitPerWindowQuote !=
toUiDecimalsForQuote(bank.netBorrowLimitPerWindowQuote)
) {
console.log(
`${
bank.name
} new - ${netBorrowLimitPerWindowQuote.toLocaleString()}, old - ${toUiDecimalsForQuote(
bank.netBorrowLimitPerWindowQuote,
).toLocaleString()}`,
);
}
}
// Deposit limits
if (bank.maintAssetWeight.toNumber() > 0) {
{
// Find asset's largest batch in $ we would need to liquidate, batches are extreme points of a range of price drop,
// range is constrained by leverage provided
// i.e. how much volatility we expect
const r = findLargestAssetBatchUi(
pisForLiqor,
bank.name,
Math.round(bank.maintAssetWeight.toNumber() * 100),
100 - Math.round(bank.maintAssetWeight.toNumber() * 100),
stepSize,
);
// eslint-disable-next-line no-constant-condition
if (false) {
if (bank.maintAssetWeight.toNumber() > 0) {
{
// Find asset's largest batch in $ we would need to liquidate, batches are extreme points of a range of price drop,
// range is constrained by leverage provided
// i.e. how much volatility we expect
const r = findLargestAssetBatchUi(
pisForLiqor,
bank.name,
Math.round(bank.maintAssetWeight.toNumber() * 100),
100 - Math.round(bank.maintAssetWeight.toNumber() * 100),
stepSize,
);
const maxLiqBatchQuoteUi = r[0];
const maxLiqBatchUi = r[1];
const maxLiqBatchQuoteUi = r[0];
const maxLiqBatchUi = r[1];
const sellImpact = getPriceImpactForBank(
midPriceImpacts,
bank,
(bank.liquidationFee.toNumber() * 100) / 2,
);
const sellImpact = getPriceImpactForBank(
midPriceImpacts,
bank,
(bank.liquidationFee.toNumber() * 100) / 2,
);
// Deposit limit = sell impact - largest batch
const allowedNewDepositsQuoteUi =
sellImpact.target_amount - maxLiqBatchQuoteUi;
const allowedNewDepositsUi =
sellImpact.target_amount / bank.uiPrice -
maxLiqBatchQuoteUi / bank.uiPrice;
// Deposit limit = sell impact - largest batch
const allowedNewDepositsQuoteUi =
sellImpact.target_amount - maxLiqBatchQuoteUi;
const allowedNewDepositsUi =
sellImpact.target_amount / bank.uiPrice -
maxLiqBatchQuoteUi / bank.uiPrice;
let depositLimitUi = bank.uiDeposits() + allowedNewDepositsUi;
const depositLimitUi = bank.uiDeposits() + allowedNewDepositsUi;
if (bank.name == 'JitoSOL') {
depositLimitUi = Math.min(depositLimitUi, 12_000);
}
if (bank.name == 'bSOL') {
depositLimitUi = Math.min(depositLimitUi, 6_000);
}
if (bank.name == 'MSOL') {
depositLimitUi = Math.min(depositLimitUi, 50_000);
}
if (bank.name == 'JLP') {
depositLimitUi = Math.min(depositLimitUi, 300_000);
}
if (bank.name == 'RAY') {
depositLimitUi = Math.min(depositLimitUi, 300_000);
}
if (bank.name == 'wBTC (Portal)') {
depositLimitUi = Math.max(depositLimitUi, 7.5);
}
if (bank.name == 'SOL') {
depositLimitUi = Math.max(depositLimitUi, 50_000);
}
// LOG
// console.log(
// `${bank.name.padStart(20)} ${maxLiqBatchUi
// .toLocaleString()
// .padStart(15)} ${maxLiqBatchQuoteUi
// .toLocaleString()
// .padStart(15)}$ ${sellImpact.target_amount
// .toLocaleString()
// .padStart(12)}$ ${sellImpact.avg_price_impact_percent
// .toLocaleString()
// .padStart(12)}% ${allowedNewDepositsUi
// .toLocaleString()
// .padStart(20)}${allowedNewDepositsQuoteUi
// .toLocaleString()
// .padStart(20)}$ ${bank
// .uiDeposits()
// .toLocaleString()
// .padStart(12)} ${(bank.uiDeposits() * bank.uiPrice)
// .toLocaleString()
// .padStart(12)}$ ${depositLimitUi
// .toLocaleString()
// .padStart(12)}`,
// );
console.log(
`${bank.name.padStart(20)} ${maxLiqBatchUi
.toLocaleString()
.padStart(15)} ${maxLiqBatchQuoteUi
.toLocaleString()
.padStart(15)}$ ${sellImpact.target_amount
.toLocaleString()
.padStart(12)}$ ${sellImpact.avg_price_impact_percent
.toLocaleString()
.padStart(12)}% ${allowedNewDepositsUi
.toLocaleString()
.padStart(20)}${allowedNewDepositsQuoteUi
.toLocaleString()
.padStart(20)}$ ${bank
.uiDeposits()
.toLocaleString()
.padStart(12)} ${(bank.uiDeposits() * bank.uiPrice)
.toLocaleString()
.padStart(12)}$ ${depositLimitUi
.toLocaleString()
.padStart(12)}`,
);
builder.depositLimit(toNative(depositLimitUi, bank.mintDecimals));
change = true;
builder.depositLimit(
toNative(depositLimitUi, bank.mintDecimals),
);
change = true;
}
}
}
const params = builder.build();
console.log(bank.name);
console.log(params.loanFeeRate);
console.log(params.loanOriginationFeeRate);
console.log(params.flashLoanSwapFeeRate);
const ix = await client.program.methods
.tokenEdit(
@ -341,6 +371,11 @@ async function updateTokenParams(): Promise<void> {
params.maintWeightShiftAbort ?? false,
false, // setFallbackOracle, unused
params.depositLimit,
params.zeroUtilRate,
params.platformLiquidationFee,
params.disableAssetLiquidation,
params.collateralFeePerDay,
params.forceWithdraw,
)
.accounts({
group: group.publicKey,
@ -401,7 +436,9 @@ async function updateTokenParams(): Promise<void> {
walletSigner,
MANGO_DAO_WALLET_GOVERNANCE,
tokenOwnerRecord,
PROPOSAL_TITLE ? PROPOSAL_TITLE : 'Update deposit limits for tokens',
PROPOSAL_TITLE
? PROPOSAL_TITLE
: 'Update loan fee, loan origination fee, and flash loan fees in mango-v4',
PROPOSAL_LINK ?? '',
Object.values(proposals).length,
instructions,

View File

@ -24,6 +24,7 @@ describe('Mango Account', () => {
new BN(0),
new BN(0),
0,
0,
[],
[],
[],

View File

@ -3,17 +3,11 @@ import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
import { OpenOrders, Order, Orderbook } from '@project-serum/serum/lib/market';
import { AccountInfo, PublicKey } from '@solana/web3.js';
import { MangoClient } from '../client';
import {
OPENBOOK_PROGRAM_ID,
RUST_I64_MAX,
RUST_I64_MIN,
USDC_MINT,
} from '../constants';
import { OPENBOOK_PROGRAM_ID, RUST_I64_MAX, RUST_I64_MIN } from '../constants';
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
import {
U64_MAX_BN,
roundTo5,
toNative,
toNativeI80F48,
toUiDecimals,
toUiDecimalsForQuote,
@ -50,6 +44,7 @@ export class MangoAccount {
buybackFeesAccruedCurrent: BN;
buybackFeesAccruedPrevious: BN;
buybackFeesExpiryTimestamp: BN;
sequenceNumber: number;
headerVersion: number;
tokens: unknown;
serum3: unknown;
@ -74,6 +69,7 @@ export class MangoAccount {
obj.buybackFeesAccruedCurrent,
obj.buybackFeesAccruedPrevious,
obj.buybackFeesExpiryTimestamp,
obj.sequenceNumber,
obj.headerVersion,
obj.tokens as TokenPositionDto[],
obj.serum3 as Serum3PositionDto[],
@ -100,6 +96,7 @@ export class MangoAccount {
public buybackFeesAccruedCurrent: BN,
public buybackFeesAccruedPrevious: BN,
public buybackFeesExpiryTimestamp: BN,
public sequenceNumber: number,
public headerVersion: number,
tokens: TokenPositionDto[],
serum3: Serum3PositionDto[],
@ -669,6 +666,15 @@ export class MangoAccount {
maxSource = maxSource.min(equivalentSourceAmount);
}
// Apply max swap fee
const maxSwapFeeRate = I80F48.fromNumber(
Math.max(
sourceBank.flashLoanSwapFeeRate,
targetBank.flashLoanSwapFeeRate,
),
);
maxSource = maxSource.div(ONE_I80F48().add(maxSwapFeeRate));
return toUiDecimals(maxSource, group.getMintDecimals(sourceMintPk));
}
@ -2145,6 +2151,13 @@ export class TokenConditionalSwap {
sellBank.tokenIndex,
liqorTcsChunkSizeInUsd,
);
if (buyTokenPriceImpact <= 0 || sellTokenPriceImpact <= 0) {
throw new Error(
`Error compitong slippage/premium for token conditional swap!`,
);
}
return (
((1 + buyTokenPriceImpact / 100) * (1 + sellTokenPriceImpact / 100) - 1) *
100

View File

@ -1,4 +1,10 @@
import { AnchorProvider, BN, Program, Wallet } from '@coral-xyz/anchor';
import {
AnchorProvider,
BN,
Program,
Provider,
Wallet,
} from '@coral-xyz/anchor';
import { OpenOrders, decodeEventQueue } from '@project-serum/serum';
import {
createAccount,
@ -63,6 +69,7 @@ import {
} from './accounts/serum3';
import {
IxGateParams,
PerpEditParams,
TokenEditParams,
TokenRegisterParams,
buildIxGate,
@ -1027,6 +1034,20 @@ export class MangoClient {
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
}
public async sequenceCheckIx(
group: Group,
mangoAccount: MangoAccount,
): Promise<TransactionInstruction> {
return await this.program.methods
.sequenceCheck(mangoAccount.sequenceNumber)
.accounts({
group: group.publicKey,
account: mangoAccount.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.instruction();
}
public async getMangoAccount(
mangoAccountPk: PublicKey,
loadSerum3Oo = false,
@ -2375,13 +2396,18 @@ export class MangoClient {
externalMarketPk: PublicKey,
limit?: number,
): Promise<MangoSignatureStatus> {
return await this.sendAndConfirmTransactionForGroup(group, [
await this.serum3CancelAllOrdersIx(
const [cancelAllIx, settle] = await Promise.all([
this.serum3CancelAllOrdersIx(
group,
mangoAccount,
externalMarketPk,
limit,
),
this.serum3SettleFundsV2Ix(group, mangoAccount, externalMarketPk),
]);
return await this.sendAndConfirmTransactionForGroup(group, [
cancelAllIx,
settle,
]);
}
@ -3942,8 +3968,9 @@ export class MangoClient {
);
const lowerLimit = 0;
const upperLimit = thresholdPriceNativeNative;
console.log(thresholdPriceNativeNative);
return await this.tokenConditionalSwapCreateIx(
return await this.tokenConditionalSwapCreatePremiumAuctionIx(
group,
account,
sellBank,
@ -3958,6 +3985,9 @@ export class MangoClient {
false,
expiryTimestamp,
thresholdPriceInSellPerBuyToken,
120,
2,
10,
);
}
@ -4016,7 +4046,7 @@ export class MangoClient {
const lowerLimit = thresholdPriceNativeNative;
const upperLimit = Number.MAX_SAFE_INTEGER;
return await this.tokenConditionalSwapCreateIx(
return await this.tokenConditionalSwapCreatePremiumAuctionIx(
group,
account,
sellBank,
@ -4031,6 +4061,9 @@ export class MangoClient {
false,
expiryTimestamp,
thresholdPriceInSellPerBuyToken,
120,
2,
10,
);
}
@ -4092,7 +4125,7 @@ export class MangoClient {
const lowerLimit = 0;
const upperLimit = thresholdPriceNativeNative;
return await this.tokenConditionalSwapCreateIx(
return await this.tokenConditionalSwapCreatePremiumAuctionIx(
group,
account,
sellBank,
@ -4107,6 +4140,9 @@ export class MangoClient {
allowMargin ?? false,
expiryTimestamp,
thresholdPriceInSellPerBuyToken,
120,
2,
10,
);
}
@ -4168,7 +4204,7 @@ export class MangoClient {
const lowerLimit = thresholdPriceNativeNative;
const upperLimit = Number.MAX_SAFE_INTEGER;
return await this.tokenConditionalSwapCreateIx(
return await this.tokenConditionalSwapCreatePremiumAuctionIx(
group,
account,
sellBank,
@ -4183,6 +4219,9 @@ export class MangoClient {
allowMargin ?? false,
expiryTimestamp,
thresholdPriceInSellPerBuyToken,
120,
2,
10,
);
}
@ -4224,7 +4263,7 @@ export class MangoClient {
maxBuy,
maxSell,
);
const pricePremiumRate = pricePremium > 0 ? pricePremium / 100 : 0.03;
const pricePremiumRate = pricePremium / 100;
let intention: TokenConditionalSwapIntention;
switch (tcsIntention) {
@ -4458,8 +4497,8 @@ export class MangoClient {
account: MangoAccount,
sellBank: Bank,
buyBank: Bank,
lowerLimit: number,
upperLimit: number,
lowerLimitNative: number,
upperLimitNative: number,
maxBuy: number,
maxSell: number,
tcsIntention:
@ -4473,19 +4512,10 @@ export class MangoClient {
allowCreatingBorrows: boolean,
expiryTimestamp: number | null,
displayPriceInSellTokenPerBuyToken: boolean,
durationSeconds: number,
durationSeconds,
premiumMultiplier = 1,
extraPricePremiumBps = 0,
): Promise<TransactionInstruction[]> {
const lowerLimitNative = toNativeSellPerBuyTokenPrice(
lowerLimit,
sellBank,
buyBank,
);
const upperLimitNative = toNativeSellPerBuyTokenPrice(
upperLimit,
sellBank,
buyBank,
);
let maxBuyNative, maxSellNative, buyAmountInUsd, sellAmountInUsd;
if (maxBuy == Number.MAX_SAFE_INTEGER) {
maxBuyNative = U64_MAX_BN;
@ -4520,13 +4550,21 @@ export class MangoClient {
sellBank.tokenIndex,
liqorTcsChunkSizeInUsd,
);
if (buyTokenPriceImpact <= 0 || sellTokenPriceImpact <= 0) {
throw new Error(
`Error compitong slippage/premium for token conditional swap!`,
);
}
maxPricePremiumPercent =
((1 + buyTokenPriceImpact / 100) * (1 + sellTokenPriceImpact / 100) -
1) *
100;
}
const maxPricePremiumRate =
maxPricePremiumPercent > 0 ? maxPricePremiumPercent / 100 : 0.03;
let maxPricePremiumRate = maxPricePremiumPercent / 100;
maxPricePremiumRate =
maxPricePremiumRate * premiumMultiplier + extraPricePremiumBps / 10000;
let intention: TokenConditionalSwapIntention;
switch (tcsIntention) {

View File

@ -49,7 +49,7 @@ export const DefaultTokenRegisterParams: TokenRegisterParams = {
adjustmentFactor: 0.004,
},
loanFeeRate: 0.0005,
loanOriginationFeeRate: 0.005,
loanOriginationFeeRate: 0.0075,
maintAssetWeight: 0,
initAssetWeight: 0,
maintLiabWeight: 1.4,
@ -310,6 +310,7 @@ export interface IxGateParams {
TokenConditionalSwapCreateLinearAuction: boolean;
Serum3PlaceOrderV2: boolean;
TokenForceWithdraw: boolean;
SequenceCheck: boolean;
}
// Default with all ixs enabled, use with buildIxGate
@ -390,6 +391,7 @@ export const TrueIxGateParams: IxGateParams = {
TokenConditionalSwapCreateLinearAuction: true,
Serum3PlaceOrderV2: true,
TokenForceWithdraw: true,
SequenceCheck: true,
};
// build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(),
@ -480,6 +482,7 @@ export function buildIxGate(p: IxGateParams): BN {
toggleIx(ixGate, p, 'TokenConditionalSwapCreateLinearAuction', 70);
toggleIx(ixGate, p, 'Serum3PlaceOrderV2', 71);
toggleIx(ixGate, p, 'TokenForceWithdraw', 72);
toggleIx(ixGate, p, 'SequenceCheck', 73);
return ixGate;
}

View File

@ -21,13 +21,13 @@ export const OPENBOOK_PROGRAM_ID = {
};
export const MANGO_V4_ID = {
testnet: new PublicKey('4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg'),
devnet: new PublicKey('4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg'),
'mainnet-beta': new PublicKey('4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg'),
testnet: new PublicKey('zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25'),
devnet: new PublicKey('zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25'),
'mainnet-beta': new PublicKey('zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25'),
};
export const MANGO_V4_MAIN_GROUP = new PublicKey(
'78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX',
'AKeMSYiJekyKfwCc3CUfVNDVAiqk9FfbQVMY3G7RUZUf',
);
export const USDC_MINT = new PublicKey(

View File

@ -1,5 +1,5 @@
export type MangoV4 = {
"version": "0.22.0",
"version": "0.23.0",
"name": "mango_v4",
"instructions": [
{
@ -1760,6 +1760,36 @@ export type MangoV4 = {
}
]
},
{
"name": "sequenceCheck",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false,
"relations": [
"group",
"owner"
]
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "expectedSequenceNumber",
"type": "u8"
}
]
},
{
"name": "stubOracleCreate",
"accounts": [
@ -7871,13 +7901,8 @@ export type MangoV4 = {
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
1
]
}
"name": "sequenceNumber",
"type": "u8"
},
{
"name": "netDeposits",
@ -9669,13 +9694,8 @@ export type MangoV4 = {
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
1
]
}
"name": "sequenceNumber",
"type": "u8"
},
{
"name": "netDeposits",
@ -11008,6 +11028,9 @@ export type MangoV4 = {
},
{
"name": "TokenForceWithdraw"
},
{
"name": "SequenceCheck"
}
]
}
@ -12871,6 +12894,71 @@ export type MangoV4 = {
}
]
},
{
"name": "PerpLiqBaseOrPositivePnlLogV3",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "perpMarketIndex",
"type": "u16",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "baseTransferLiqee",
"type": "i64",
"index": false
},
{
"name": "quoteTransferLiqee",
"type": "i128",
"index": false
},
{
"name": "quoteTransferLiqor",
"type": "i128",
"index": false
},
{
"name": "quotePlatformFee",
"type": "i128",
"index": false
},
{
"name": "pnlTransfer",
"type": "i128",
"index": false
},
{
"name": "pnlSettleLimitTransferRecurring",
"type": "i64",
"index": false
},
{
"name": "pnlSettleLimitTransferOneshot",
"type": "i64",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
}
]
},
{
"name": "PerpLiqBankruptcyLog",
"fields": [
@ -13888,6 +13976,46 @@ export type MangoV4 = {
"name": "fee",
"type": "i128",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
}
]
},
{
"name": "ForceWithdrawLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "quantity",
"type": "u64",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
},
{
"name": "toTokenAccount",
"type": "publicKey",
"index": false
}
]
}
@ -14242,12 +14370,22 @@ export type MangoV4 = {
"code": 6069,
"name": "TokenAssetLiquidationDisabled",
"msg": "the asset does not allow liquidation"
},
{
"code": 6070,
"name": "BorrowsRequireHealthAccountBank",
"msg": "for borrows the bank must be in the health account list"
},
{
"code": 6071,
"name": "InvalidSequenceNumber",
"msg": "invalid sequence number"
}
]
};
export const IDL: MangoV4 = {
"version": "0.22.0",
"version": "0.23.0",
"name": "mango_v4",
"instructions": [
{
@ -16008,6 +16146,36 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "sequenceCheck",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false,
"relations": [
"group",
"owner"
]
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "expectedSequenceNumber",
"type": "u8"
}
]
},
{
"name": "stubOracleCreate",
"accounts": [
@ -22119,13 +22287,8 @@ export const IDL: MangoV4 = {
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
1
]
}
"name": "sequenceNumber",
"type": "u8"
},
{
"name": "netDeposits",
@ -23917,13 +24080,8 @@ export const IDL: MangoV4 = {
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
1
]
}
"name": "sequenceNumber",
"type": "u8"
},
{
"name": "netDeposits",
@ -25256,6 +25414,9 @@ export const IDL: MangoV4 = {
},
{
"name": "TokenForceWithdraw"
},
{
"name": "SequenceCheck"
}
]
}
@ -27119,6 +27280,71 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "PerpLiqBaseOrPositivePnlLogV3",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "perpMarketIndex",
"type": "u16",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "baseTransferLiqee",
"type": "i64",
"index": false
},
{
"name": "quoteTransferLiqee",
"type": "i128",
"index": false
},
{
"name": "quoteTransferLiqor",
"type": "i128",
"index": false
},
{
"name": "quotePlatformFee",
"type": "i128",
"index": false
},
{
"name": "pnlTransfer",
"type": "i128",
"index": false
},
{
"name": "pnlSettleLimitTransferRecurring",
"type": "i64",
"index": false
},
{
"name": "pnlSettleLimitTransferOneshot",
"type": "i64",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
}
]
},
{
"name": "PerpLiqBankruptcyLog",
"fields": [
@ -28136,6 +28362,46 @@ export const IDL: MangoV4 = {
"name": "fee",
"type": "i128",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
}
]
},
{
"name": "ForceWithdrawLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "quantity",
"type": "u64",
"index": false
},
{
"name": "price",
"type": "i128",
"index": false
},
{
"name": "toTokenAccount",
"type": "publicKey",
"index": false
}
]
}
@ -28490,6 +28756,16 @@ export const IDL: MangoV4 = {
"code": 6069,
"name": "TokenAssetLiquidationDisabled",
"msg": "the asset does not allow liquidation"
},
{
"code": 6070,
"name": "BorrowsRequireHealthAccountBank",
"msg": "for borrows the bank must be in the health account list"
},
{
"code": 6071,
"name": "InvalidSequenceNumber",
"msg": "invalid sequence number"
}
]
};

View File

@ -30,10 +30,10 @@
dependencies:
regenerator-runtime "^0.14.0"
"@blockworks-foundation/mango-v4-settings@0.4.10":
version "0.4.10"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.4.10.tgz#7a30db53c81abea56c2dd83e928259246e3b4e7d"
integrity sha512-J4RhfhcNmBn0CeqjN4mlaou+vPW9NGpWlbkx1+ZczsJw6r5tIkQ82MDqKMKvyIgn457krRVdJ9+gLmHEln4QfA==
"@blockworks-foundation/mango-v4-settings@0.14.15":
version "0.14.15"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.14.15.tgz#0ac04f2cffbd27a4129bc2086ec1418e29393880"
integrity sha512-GBo43KCosdaohbtS3Rtz8e2zsLsiN3JqTfLKVtStRYO3f2tQLzeo/D2Khz07psw2egq6TCNZJH7+v32iGrPq0w==
dependencies:
bn.js "^5.2.1"
eslint-config-prettier "^9.0.0"