Merge remote-tracking branch 'origin/dev' into cj/avgentryprice
This commit is contained in:
commit
dc4a770835
|
@ -0,0 +1,2 @@
|
|||
ts/client/src/mango_v4.ts
|
||||
ts/client/src/scripts
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
ts/client/src/mango_v4.ts
|
|
@ -12,7 +12,7 @@ url = "https://anchor.projectserum.com"
|
|||
|
||||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "/home/mc/.config/solana/id.json"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 anchor-tests/*.ts"
|
||||
|
|
|
@ -1260,17 +1260,24 @@ dependencies = [
|
|||
"anchor-lang 0.25.0",
|
||||
"anchor-spl 0.25.0",
|
||||
"anyhow",
|
||||
"base64 0.13.0",
|
||||
"bincode",
|
||||
"fixed",
|
||||
"fixed-macro",
|
||||
"itertools 0.10.3",
|
||||
"log 0.4.17",
|
||||
"mango-v4",
|
||||
"pyth-sdk-solana",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serum_dex 0.4.0 (git+https://github.com/blockworks-foundation/serum-dex.git)",
|
||||
"shellexpand",
|
||||
"solana-account-decoder",
|
||||
"solana-client",
|
||||
"solana-sdk",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4581,9 +4588,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.10"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
|
||||
checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64 0.13.0",
|
||||
|
@ -4607,14 +4614,15 @@ dependencies = [
|
|||
"percent-encoding 2.1.0",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pemfile 0.3.0",
|
||||
"rustls-pemfile 1.0.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
"tokio-util 0.6.10",
|
||||
"tokio-util 0.7.2",
|
||||
"tower-service",
|
||||
"url 2.2.2",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
|
@ -4762,15 +4770,6 @@ dependencies = [
|
|||
"base64 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.0"
|
||||
|
@ -4947,9 +4946,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.137"
|
||||
version = "1.0.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
||||
checksum = "7af873f2c95b99fcb0bd0fe622a43e29514658873c8ceba88c4cb88833a22500"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -4965,9 +4964,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.137"
|
||||
version = "1.0.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
|
||||
checksum = "75743a150d003dd863b51dc809bcad0d73f2102c53632f1e954e738192a3413f"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.39",
|
||||
"quote 1.0.18",
|
||||
|
@ -4987,9 +4986,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.81"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
|
||||
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
|
2
anchor
2
anchor
|
@ -1 +1 @@
|
|||
Subproject commit 442c00634e847af84672727dc00b45a2c8d0a956
|
||||
Subproject commit b52f23614601652a99ec6c27aec77bd327363b31
|
|
@ -1,15 +1,10 @@
|
|||
import * as anchor from '@project-serum/anchor';
|
||||
import { Program, AnchorProvider } from '@project-serum/anchor';
|
||||
import { MangoV4 } from '../target/types/mango_v4';
|
||||
import * as spl from '@solana/spl-token';
|
||||
import { AnchorProvider, Program } from '@project-serum/anchor';
|
||||
import NodeWallet from '@project-serum/anchor/dist/cjs/nodewallet';
|
||||
import { PublicKey, LAMPORTS_PER_SOL, Connection } from '@solana/web3.js';
|
||||
import {
|
||||
Group,
|
||||
MangoClient,
|
||||
StubOracle,
|
||||
AccountSize,
|
||||
} from '../ts/client/src/index';
|
||||
import * as spl from '@solana/spl-token';
|
||||
import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
|
||||
import { MangoV4 } from '../target/types/mango_v4';
|
||||
import { Group, MangoClient, StubOracle } from '../ts/client/src/index';
|
||||
|
||||
enum MINTS {
|
||||
USDC = 'USDC',
|
||||
|
@ -118,7 +113,7 @@ describe('mango-v4', () => {
|
|||
// I think this is only for getting the serum market though?
|
||||
envClient = await MangoClient.connect(envProvider, 'devnet', programId);
|
||||
await envClient.groupCreate(groupNum, false, 1, insuranceMintPk);
|
||||
group = await envClient.getGroupForAdmin(adminPk, groupNum);
|
||||
group = await envClient.getGroupForCreator(adminPk, groupNum);
|
||||
|
||||
await envClient.stubOracleCreate(group, mintsMap['USDC']!.publicKey, 1.0);
|
||||
usdcOracle = (
|
||||
|
@ -215,49 +210,19 @@ describe('mango-v4', () => {
|
|||
const mangoAccount = await client.getOrCreateMangoAccount(
|
||||
group,
|
||||
users[0].keypair.publicKey,
|
||||
users[0].keypair,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
await client.tokenDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
'USDC',
|
||||
100.5,
|
||||
users[0].keypair,
|
||||
);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 100.5);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
await client.tokenDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
'BTC',
|
||||
50.5,
|
||||
users[0].keypair,
|
||||
);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 50.5);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
await client.tokenWithdraw(
|
||||
group,
|
||||
mangoAccount,
|
||||
'USDC',
|
||||
100,
|
||||
false,
|
||||
users[0].keypair,
|
||||
);
|
||||
await client.tokenWithdraw(group, mangoAccount, 'USDC', 100, false);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
await client.tokenWithdraw(
|
||||
group,
|
||||
mangoAccount,
|
||||
'BTC',
|
||||
50,
|
||||
false,
|
||||
users[0].keypair,
|
||||
);
|
||||
await client.tokenWithdraw(group, mangoAccount, 'BTC', 50, false);
|
||||
await mangoAccount.reload(client, group);
|
||||
});
|
||||
|
||||
|
@ -284,59 +249,26 @@ describe('mango-v4', () => {
|
|||
const mangoAccountA = await clientA.getOrCreateMangoAccount(
|
||||
group,
|
||||
users[0].keypair.publicKey,
|
||||
users[0].keypair,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
await mangoAccountA.reload(clientA, group);
|
||||
|
||||
const mangoAccountB = await clientB.getOrCreateMangoAccount(
|
||||
group,
|
||||
users[1].keypair.publicKey,
|
||||
users[1].keypair,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
await mangoAccountB.reload(clientB, group);
|
||||
|
||||
await envClient.stubOracleSet(group, btcOracle.publicKey, 100);
|
||||
|
||||
// Initialize liquidator
|
||||
await clientA.tokenDeposit(
|
||||
group,
|
||||
mangoAccountA,
|
||||
'USDC',
|
||||
1000,
|
||||
users[0].keypair,
|
||||
);
|
||||
await clientA.tokenDeposit(
|
||||
group,
|
||||
mangoAccountA,
|
||||
'BTC',
|
||||
100,
|
||||
users[0].keypair,
|
||||
);
|
||||
await clientA.tokenDeposit(group, mangoAccountA, 'USDC', 1000);
|
||||
await clientA.tokenDeposit(group, mangoAccountA, 'BTC', 100);
|
||||
|
||||
// Deposit collateral
|
||||
await clientB.tokenDeposit(
|
||||
group,
|
||||
mangoAccountB,
|
||||
'BTC',
|
||||
100,
|
||||
users[1].keypair,
|
||||
);
|
||||
await clientB.tokenDeposit(group, mangoAccountB, 'BTC', 100);
|
||||
await mangoAccountB.reload(clientB, group);
|
||||
// // Borrow
|
||||
await clientB.tokenWithdraw(
|
||||
group,
|
||||
mangoAccountB,
|
||||
'USDC',
|
||||
200,
|
||||
true,
|
||||
users[1].keypair,
|
||||
);
|
||||
await clientB.tokenWithdraw(group, mangoAccountB, 'USDC', 200, true);
|
||||
// // Set price so health is below maintanence
|
||||
await envClient.stubOracleSet(group, btcOracle.publicKey, 1);
|
||||
|
||||
|
@ -346,10 +278,13 @@ describe('mango-v4', () => {
|
|||
group,
|
||||
mangoAccountA,
|
||||
mangoAccountB,
|
||||
users[0].keypair,
|
||||
'BTC',
|
||||
'USDC',
|
||||
1000,
|
||||
);
|
||||
});
|
||||
|
||||
it('update index and rate', async () => {
|
||||
envClient.updateIndexAndRate(group, 'USDC');
|
||||
});
|
||||
});
|
||||
|
|
145
cli/src/main.rs
145
cli/src/main.rs
|
@ -1,6 +1,8 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use client::MangoClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[clap()]
|
||||
|
@ -9,8 +11,83 @@ struct Cli {
|
|||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
struct Rpc {
|
||||
#[clap(short, long, default_value = "m")]
|
||||
url: String,
|
||||
|
||||
#[clap(short, long, default_value = "")]
|
||||
fee_payer: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
struct CreateAccount {
|
||||
#[clap(short, long)]
|
||||
group: String,
|
||||
|
||||
/// also pays for everything
|
||||
#[clap(short, long)]
|
||||
owner: String,
|
||||
|
||||
#[clap(short, long)]
|
||||
account_num: Option<u32>,
|
||||
|
||||
#[clap(short, long, default_value = "")]
|
||||
name: String,
|
||||
|
||||
#[clap(flatten)]
|
||||
rpc: Rpc,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
struct Deposit {
|
||||
#[clap(long)]
|
||||
account: String,
|
||||
|
||||
/// also pays for everything
|
||||
#[clap(short, long)]
|
||||
owner: String,
|
||||
|
||||
#[clap(short, long)]
|
||||
mint: String,
|
||||
|
||||
#[clap(short, long)]
|
||||
amount: u64,
|
||||
|
||||
#[clap(flatten)]
|
||||
rpc: Rpc,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
struct JupiterSwap {
|
||||
#[clap(long)]
|
||||
account: String,
|
||||
|
||||
/// also pays for everything
|
||||
#[clap(short, long)]
|
||||
owner: String,
|
||||
|
||||
#[clap(long)]
|
||||
input_mint: String,
|
||||
|
||||
#[clap(long)]
|
||||
output_mint: String,
|
||||
|
||||
#[clap(short, long)]
|
||||
amount: u64,
|
||||
|
||||
#[clap(short, long)]
|
||||
slippage: f64,
|
||||
|
||||
#[clap(flatten)]
|
||||
rpc: Rpc,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
enum Command {
|
||||
CreateAccount(CreateAccount),
|
||||
Deposit(Deposit),
|
||||
JupiterSwap(JupiterSwap),
|
||||
GroupAddress {
|
||||
#[clap(short, long)]
|
||||
creator: String,
|
||||
|
@ -26,9 +103,22 @@ enum Command {
|
|||
owner: String,
|
||||
|
||||
#[clap(short, long, default_value = "0")]
|
||||
num: u8,
|
||||
num: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Rpc {
|
||||
fn client(&self, override_fee_payer: Option<&str>) -> anyhow::Result<client::Client> {
|
||||
let fee_payer = client::keypair_from_cli(override_fee_payer.unwrap_or(&self.fee_payer));
|
||||
Ok(client::Client {
|
||||
cluster: anchor_client::Cluster::from_str(&self.url)?,
|
||||
commitment: solana_sdk::commitment_config::CommitmentConfig::confirmed(),
|
||||
fee_payer: Arc::new(fee_payer),
|
||||
timeout: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
env_logger::init_from_env(
|
||||
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
|
||||
|
@ -38,6 +128,57 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Command::CreateAccount(cmd) => {
|
||||
let client = cmd.rpc.client(Some(&cmd.owner))?;
|
||||
let group = client::pubkey_from_cli(&cmd.group);
|
||||
let owner = client::keypair_from_cli(&cmd.owner);
|
||||
|
||||
let account_num = if let Some(num) = cmd.account_num {
|
||||
num
|
||||
} else {
|
||||
// find free account_num
|
||||
let accounts = MangoClient::find_accounts(&client, group, &owner)?;
|
||||
if accounts.is_empty() {
|
||||
0
|
||||
} else {
|
||||
accounts
|
||||
.iter()
|
||||
.map(|(_, account)| account.fixed.account_num)
|
||||
.max()
|
||||
.unwrap()
|
||||
+ 1
|
||||
}
|
||||
};
|
||||
let (account, txsig) = MangoClient::create_account(
|
||||
&client,
|
||||
group,
|
||||
&owner,
|
||||
&owner,
|
||||
account_num,
|
||||
&cmd.name,
|
||||
)?;
|
||||
println!("{}", account);
|
||||
println!("{}", txsig);
|
||||
}
|
||||
Command::Deposit(cmd) => {
|
||||
let client = cmd.rpc.client(Some(&cmd.owner))?;
|
||||
let account = client::pubkey_from_cli(&cmd.account);
|
||||
let owner = client::keypair_from_cli(&cmd.owner);
|
||||
let mint = client::pubkey_from_cli(&cmd.mint);
|
||||
let client = MangoClient::new_for_existing_account(client, account, owner)?;
|
||||
let txsig = client.token_deposit(mint, cmd.amount)?;
|
||||
println!("{}", txsig);
|
||||
}
|
||||
Command::JupiterSwap(cmd) => {
|
||||
let client = cmd.rpc.client(Some(&cmd.owner))?;
|
||||
let account = client::pubkey_from_cli(&cmd.account);
|
||||
let owner = client::keypair_from_cli(&cmd.owner);
|
||||
let input_mint = client::pubkey_from_cli(&cmd.input_mint);
|
||||
let output_mint = client::pubkey_from_cli(&cmd.output_mint);
|
||||
let client = MangoClient::new_for_existing_account(client, account, owner)?;
|
||||
let txsig = client.jupiter_swap(input_mint, output_mint, cmd.amount, cmd.slippage)?;
|
||||
println!("{}", txsig);
|
||||
}
|
||||
Command::GroupAddress { creator, num } => {
|
||||
let creator = client::pubkey_from_cli(&creator);
|
||||
println!("{}", MangoClient::group_for_admin(creator, num));
|
||||
|
|
|
@ -22,3 +22,10 @@ solana-account-decoder = "~1.10.29"
|
|||
solana-client = "~1.10.29"
|
||||
solana-sdk = "~1.10.29"
|
||||
thiserror = "1.0.31"
|
||||
log = "0.4"
|
||||
reqwest = "0.11.11"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = "1.0.141"
|
||||
serde_json = "1.0.82"
|
||||
base64 = "0.13.0"
|
||||
bincode = "1.3.3"
|
|
@ -11,17 +11,20 @@ use anchor_lang::Id;
|
|||
use anchor_spl::associated_token::get_associated_token_address;
|
||||
use anchor_spl::token::Token;
|
||||
|
||||
use bincode::Options;
|
||||
use fixed::types::I80F48;
|
||||
use itertools::Itertools;
|
||||
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
||||
use mango_v4::state::{AccountSize, Bank, Group, MangoAccountValue, Serum3MarketIndex, TokenIndex};
|
||||
|
||||
use solana_client::nonblocking::rpc_client::RpcClient as RpcClientAsync;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::signer::keypair;
|
||||
|
||||
use crate::account_fetcher::*;
|
||||
use crate::context::{MangoGroupContext, Serum3MarketContext, TokenContext};
|
||||
use crate::gpa::fetch_mango_accounts;
|
||||
use crate::jupiter;
|
||||
use crate::util::MyClone;
|
||||
|
||||
use anyhow::Context;
|
||||
|
@ -36,14 +39,21 @@ pub struct Client {
|
|||
pub cluster: Cluster,
|
||||
pub fee_payer: Arc<Keypair>,
|
||||
pub commitment: CommitmentConfig,
|
||||
pub timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(cluster: Cluster, commitment: CommitmentConfig, fee_payer: &Keypair) -> Self {
|
||||
pub fn new(
|
||||
cluster: Cluster,
|
||||
commitment: CommitmentConfig,
|
||||
fee_payer: &Keypair,
|
||||
timeout: Option<Duration>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cluster,
|
||||
fee_payer: Arc::new(fee_payer.clone()),
|
||||
commitment,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,8 +65,22 @@ impl Client {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn rpc_with_timeout(&self, timeout: Duration) -> RpcClient {
|
||||
RpcClient::new_with_timeout_and_commitment(self.cluster.clone(), timeout, self.commitment)
|
||||
pub fn rpc(&self) -> RpcClient {
|
||||
let url = self.cluster.url().to_string();
|
||||
if let Some(timeout) = self.timeout.as_ref() {
|
||||
RpcClient::new_with_timeout_and_commitment(url, *timeout, self.commitment)
|
||||
} else {
|
||||
RpcClient::new_with_commitment(url, self.commitment)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rpc_async(&self) -> RpcClientAsync {
|
||||
let url = self.cluster.url().to_string();
|
||||
if let Some(timeout) = self.timeout.as_ref() {
|
||||
RpcClientAsync::new_with_timeout_and_commitment(url, *timeout, self.commitment)
|
||||
} else {
|
||||
RpcClientAsync::new_with_commitment(url, self.commitment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,6 +96,19 @@ pub struct MangoClient {
|
|||
pub mango_account_address: Pubkey,
|
||||
|
||||
pub context: MangoGroupContext,
|
||||
|
||||
// Since MangoClient currently provides a blocking interface, we'd prefer to use reqwest::blocking::Client
|
||||
// but that doesn't work inside async contexts. Hence we use the async reqwest Client instead and use
|
||||
// a manual runtime to bridge into async code from both sync and async contexts.
|
||||
// That doesn't work perfectly, see MangoClient::invoke().
|
||||
pub http_client: reqwest::Client,
|
||||
runtime: Option<tokio::runtime::Runtime>,
|
||||
}
|
||||
|
||||
impl Drop for MangoClient {
|
||||
fn drop(&mut self) {
|
||||
self.runtime.take().expect("runtime").shutdown_background();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add retry framework for sending tx and rpc calls
|
||||
|
@ -88,11 +125,20 @@ impl MangoClient {
|
|||
.0
|
||||
}
|
||||
|
||||
pub fn find_accounts(
|
||||
client: &Client,
|
||||
group: Pubkey,
|
||||
owner: &Keypair,
|
||||
) -> anyhow::Result<Vec<(Pubkey, MangoAccountValue)>> {
|
||||
let program = client.anchor_client().program(mango_v4::ID);
|
||||
fetch_mango_accounts(&program, group, owner.pubkey()).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn find_or_create_account(
|
||||
client: &Client,
|
||||
group: Pubkey,
|
||||
owner: Keypair,
|
||||
payer: Keypair, // pays the SOL for the new account
|
||||
owner: &Keypair,
|
||||
payer: &Keypair, // pays the SOL for the new account
|
||||
mango_account_name: &str,
|
||||
) -> anyhow::Result<Pubkey> {
|
||||
let program = client.anchor_client().program(mango_v4::ID);
|
||||
|
@ -113,43 +159,7 @@ impl MangoClient {
|
|||
Some(tuple) => tuple.1.fixed.account_num + 1,
|
||||
None => 0u32,
|
||||
};
|
||||
program
|
||||
.request()
|
||||
.instruction(Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::AccountCreate {
|
||||
group,
|
||||
owner: owner.pubkey(),
|
||||
account: {
|
||||
Pubkey::find_program_address(
|
||||
&[
|
||||
group.as_ref(),
|
||||
b"MangoAccount".as_ref(),
|
||||
owner.pubkey().as_ref(),
|
||||
&account_num.to_le_bytes(),
|
||||
],
|
||||
&mango_v4::id(),
|
||||
)
|
||||
.0
|
||||
},
|
||||
payer: payer.pubkey(),
|
||||
system_program: System::id(),
|
||||
},
|
||||
None,
|
||||
),
|
||||
data: anchor_lang::InstructionData::data(
|
||||
&mango_v4::instruction::AccountCreate {
|
||||
account_num,
|
||||
name: mango_account_name.to_owned(),
|
||||
account_size: AccountSize::Small,
|
||||
},
|
||||
),
|
||||
})
|
||||
.signer(&owner)
|
||||
.signer(&payer)
|
||||
.send()
|
||||
.map_err(prettify_client_error)
|
||||
Self::create_account(client, group, owner, payer, account_num, mango_account_name)
|
||||
.context("Failed to create account...")?;
|
||||
}
|
||||
let mango_account_tuples = fetch_mango_accounts(&program, group, owner.pubkey())?;
|
||||
|
@ -160,13 +170,60 @@ impl MangoClient {
|
|||
Ok(mango_account_tuples[index].0)
|
||||
}
|
||||
|
||||
pub fn create_account(
|
||||
client: &Client,
|
||||
group: Pubkey,
|
||||
owner: &Keypair,
|
||||
payer: &Keypair, // pays the SOL for the new account
|
||||
account_num: u32,
|
||||
mango_account_name: &str,
|
||||
) -> anyhow::Result<(Pubkey, Signature)> {
|
||||
let program = client.anchor_client().program(mango_v4::ID);
|
||||
let account = Pubkey::find_program_address(
|
||||
&[
|
||||
group.as_ref(),
|
||||
b"MangoAccount".as_ref(),
|
||||
owner.pubkey().as_ref(),
|
||||
&account_num.to_le_bytes(),
|
||||
],
|
||||
&mango_v4::id(),
|
||||
)
|
||||
.0;
|
||||
let txsig = program
|
||||
.request()
|
||||
.instruction(Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::AccountCreate {
|
||||
group,
|
||||
owner: owner.pubkey(),
|
||||
account,
|
||||
payer: payer.pubkey(),
|
||||
system_program: System::id(),
|
||||
},
|
||||
None,
|
||||
),
|
||||
data: anchor_lang::InstructionData::data(&mango_v4::instruction::AccountCreate {
|
||||
account_num,
|
||||
name: mango_account_name.to_owned(),
|
||||
account_size: AccountSize::Small,
|
||||
}),
|
||||
})
|
||||
.signer(owner)
|
||||
.signer(payer)
|
||||
.send()
|
||||
.map_err(prettify_client_error)?;
|
||||
|
||||
Ok((account, txsig))
|
||||
}
|
||||
|
||||
/// Conveniently creates a RPC based client
|
||||
pub fn new_for_existing_account(
|
||||
client: Client,
|
||||
account: Pubkey,
|
||||
owner: Keypair,
|
||||
) -> anyhow::Result<Self> {
|
||||
let rpc = client.rpc_with_timeout(Duration::from_secs(60));
|
||||
let rpc = client.rpc();
|
||||
let account_fetcher = Arc::new(CachedAccountFetcher::new(RpcAccountFetcher { rpc }));
|
||||
let mango_account = account_fetcher_fetch_mango_account(&*account_fetcher, account)?;
|
||||
let group = mango_account.fixed.group;
|
||||
|
@ -199,6 +256,15 @@ impl MangoClient {
|
|||
owner,
|
||||
mango_account_address: account,
|
||||
context: group_context,
|
||||
http_client: reqwest::Client::new(),
|
||||
runtime: Some(
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.thread_name("mango-client")
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -229,13 +295,13 @@ impl MangoClient {
|
|||
|
||||
pub fn derive_health_check_remaining_account_metas(
|
||||
&self,
|
||||
affected_token: Option<TokenIndex>,
|
||||
affected_tokens: Vec<TokenIndex>,
|
||||
writable_banks: bool,
|
||||
) -> anyhow::Result<Vec<AccountMeta>> {
|
||||
let account = self.mango_account()?;
|
||||
self.context.derive_health_check_remaining_account_metas(
|
||||
&account,
|
||||
affected_token,
|
||||
affected_tokens,
|
||||
writable_banks,
|
||||
)
|
||||
}
|
||||
|
@ -273,12 +339,6 @@ impl MangoClient {
|
|||
.chain(account.perp_iter_active_accounts())
|
||||
.map(|&pa| self.context.perp_market_address(pa.market_index));
|
||||
|
||||
let to_account_meta = |pubkey| AccountMeta {
|
||||
pubkey,
|
||||
is_writable: false,
|
||||
is_signer: false,
|
||||
};
|
||||
|
||||
Ok(banks
|
||||
.iter()
|
||||
.map(|(pubkey, is_writable)| AccountMeta {
|
||||
|
@ -286,18 +346,19 @@ impl MangoClient {
|
|||
is_writable: *is_writable,
|
||||
is_signer: false,
|
||||
})
|
||||
.chain(oracles.into_iter().map(to_account_meta))
|
||||
.chain(perp_markets.map(to_account_meta))
|
||||
.chain(serum_oos.map(to_account_meta))
|
||||
.chain(oracles.into_iter().map(to_readonly_account_meta))
|
||||
.chain(perp_markets.map(to_readonly_account_meta))
|
||||
.chain(serum_oos.map(to_readonly_account_meta))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn token_deposit(&self, token_name: &str, amount: u64) -> anyhow::Result<Signature> {
|
||||
let token_index = *self.context.token_indexes_by_name.get(token_name).unwrap();
|
||||
let mint_info = self.context.mint_info(token_index);
|
||||
pub fn token_deposit(&self, mint: Pubkey, amount: u64) -> anyhow::Result<Signature> {
|
||||
let token = self.context.token_by_mint(&mint)?;
|
||||
let token_index = token.token_index;
|
||||
let mint_info = token.mint_info;
|
||||
|
||||
let health_check_metas =
|
||||
self.derive_health_check_remaining_account_metas(Some(token_index), false)?;
|
||||
self.derive_health_check_remaining_account_metas(vec![token_index], false)?;
|
||||
|
||||
self.program()
|
||||
.request()
|
||||
|
@ -430,7 +491,7 @@ impl MangoClient {
|
|||
let account = self.mango_account()?;
|
||||
let open_orders = account.serum3_find(s3.market_index).unwrap().open_orders;
|
||||
|
||||
let health_check_metas = self.derive_health_check_remaining_account_metas(None, false)?;
|
||||
let health_check_metas = self.derive_health_check_remaining_account_metas(vec![], false)?;
|
||||
|
||||
// https://github.com/project-serum/serum-ts/blob/master/packages/serum/src/market.ts#L1306
|
||||
let limit_price = {
|
||||
|
@ -719,11 +780,7 @@ impl MangoClient {
|
|||
.mint_info
|
||||
.banks()
|
||||
.iter()
|
||||
.map(|bank_pubkey| AccountMeta {
|
||||
pubkey: *bank_pubkey,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
})
|
||||
.map(|bank_pubkey| to_writable_account_meta(*bank_pubkey))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let health_remaining_ams = self
|
||||
|
@ -772,6 +829,223 @@ impl MangoClient {
|
|||
.send()
|
||||
.map_err(prettify_client_error)
|
||||
}
|
||||
|
||||
pub fn jupiter_swap(
|
||||
&self,
|
||||
input_mint: Pubkey,
|
||||
output_mint: Pubkey,
|
||||
source_amount: u64,
|
||||
slippage: f64,
|
||||
) -> anyhow::Result<Signature> {
|
||||
self.invoke(self.jupiter_swap_async(input_mint, output_mint, source_amount, slippage))
|
||||
}
|
||||
|
||||
// Not actually fully async, since it uses the blocking RPC client to send the actual tx
|
||||
pub async fn jupiter_swap_async(
|
||||
&self,
|
||||
input_mint: Pubkey,
|
||||
output_mint: Pubkey,
|
||||
source_amount: u64,
|
||||
slippage: f64,
|
||||
) -> anyhow::Result<Signature> {
|
||||
let source_token = self.context.token_by_mint(&input_mint)?;
|
||||
let target_token = self.context.token_by_mint(&output_mint)?;
|
||||
|
||||
let quote = self
|
||||
.http_client
|
||||
.get("https://quote-api.jup.ag/v1/quote")
|
||||
.query(&[
|
||||
("inputMint", input_mint.to_string()),
|
||||
("outputMint", output_mint.to_string()),
|
||||
("amount", format!("{}", source_amount)),
|
||||
("onlyDirectRoutes", "true".into()),
|
||||
("filterTopNResult", "10".into()),
|
||||
("slippage", format!("{}", slippage)),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.context("quote request to jupiter")?
|
||||
.json::<jupiter::QueryResult>()
|
||||
.await
|
||||
.context("receiving json response from jupiter quote request")?;
|
||||
|
||||
// Find the top route that doesn't involve Raydium (that has too many accounts)
|
||||
let route = quote
|
||||
.data
|
||||
.iter()
|
||||
.find(|route| {
|
||||
!route
|
||||
.market_infos
|
||||
.iter()
|
||||
.any(|mi| mi.label.contains("Raydium"))
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"no route for swap. found {} routes, but none were usable",
|
||||
quote.data.len()
|
||||
)
|
||||
})?;
|
||||
|
||||
let swap = self
|
||||
.http_client
|
||||
.post("https://quote-api.jup.ag/v1/swap")
|
||||
.json(&jupiter::SwapRequest {
|
||||
route: route.clone(),
|
||||
user_public_key: self.owner.pubkey().to_string(),
|
||||
wrap_unwrap_sol: false,
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.context("swap transaction request to jupiter")?
|
||||
.json::<jupiter::SwapResponse>()
|
||||
.await
|
||||
.context("receiving json response from jupiter swap transaction request")?;
|
||||
|
||||
if swap.setup_transaction.is_some() || swap.cleanup_transaction.is_some() {
|
||||
anyhow::bail!(
|
||||
"chosen jupiter route requires setup or cleanup transactions, can't execute"
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: deal with versioned transaction!
|
||||
let jup_tx = bincode::options()
|
||||
.with_fixint_encoding()
|
||||
.reject_trailing_bytes()
|
||||
.deserialize::<solana_sdk::transaction::Transaction>(
|
||||
&base64::decode(&swap.swap_transaction)
|
||||
.context("base64 decoding jupiter transaction")?,
|
||||
)
|
||||
.context("parsing jupiter transaction")?;
|
||||
let jup_ixs = deserialize_instructions(&jup_tx.message)
|
||||
.into_iter()
|
||||
// TODO: possibly creating associated token accounts if they don't exist yet is good?!
|
||||
// we could squeeze the FlashLoan instructions in the middle:
|
||||
// - beginning AToken...
|
||||
// - FlashLoanBegin
|
||||
// - other JUP ix
|
||||
// - FlashLoanEnd
|
||||
// - ending AToken
|
||||
.filter(|ix| {
|
||||
ix.program_id
|
||||
!= Pubkey::from_str("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL").unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let bank_ams = [
|
||||
source_token.mint_info.first_bank(),
|
||||
target_token.mint_info.first_bank(),
|
||||
]
|
||||
.into_iter()
|
||||
.map(to_writable_account_meta)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let vault_ams = [
|
||||
source_token.mint_info.first_vault(),
|
||||
target_token.mint_info.first_vault(),
|
||||
]
|
||||
.into_iter()
|
||||
.map(to_writable_account_meta)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let token_ams = [source_token.mint_info.mint, target_token.mint_info.mint]
|
||||
.into_iter()
|
||||
.map(|mint| {
|
||||
to_writable_account_meta(
|
||||
anchor_spl::associated_token::get_associated_token_address(
|
||||
&self.owner(),
|
||||
&mint,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let loan_amounts = vec![source_amount, 0u64];
|
||||
|
||||
// This relies on the fact that health account banks will be identical to the first_bank above!
|
||||
let health_ams = self
|
||||
.derive_health_check_remaining_account_metas(
|
||||
vec![source_token.token_index, target_token.token_index],
|
||||
true,
|
||||
)
|
||||
.context("building health accounts")?;
|
||||
|
||||
let program = self.program();
|
||||
let mut builder = program.request().instruction(Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: {
|
||||
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::FlashLoanBegin {
|
||||
group: self.group(),
|
||||
token_program: Token::id(),
|
||||
instructions: solana_sdk::sysvar::instructions::id(),
|
||||
},
|
||||
None,
|
||||
);
|
||||
ams.extend(bank_ams);
|
||||
ams.extend(vault_ams.clone());
|
||||
ams.extend(token_ams.clone());
|
||||
ams
|
||||
},
|
||||
data: anchor_lang::InstructionData::data(&mango_v4::instruction::FlashLoanBegin {
|
||||
loan_amounts,
|
||||
}),
|
||||
});
|
||||
for ix in jup_ixs {
|
||||
builder = builder.instruction(ix);
|
||||
}
|
||||
builder = builder.instruction(Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: {
|
||||
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::FlashLoanEnd {
|
||||
account: self.mango_account_address,
|
||||
owner: self.owner(),
|
||||
token_program: Token::id(),
|
||||
},
|
||||
None,
|
||||
);
|
||||
ams.extend(health_ams);
|
||||
ams.extend(vault_ams);
|
||||
ams.extend(token_ams);
|
||||
ams
|
||||
},
|
||||
data: anchor_lang::InstructionData::data(&mango_v4::instruction::FlashLoanEnd {}),
|
||||
});
|
||||
|
||||
let rpc = self.client.rpc_async();
|
||||
builder
|
||||
.signer(&self.owner)
|
||||
.send_rpc_async(&rpc)
|
||||
.await
|
||||
.map_err(prettify_client_error)
|
||||
}
|
||||
|
||||
fn invoke<T, F: std::future::Future<Output = T>>(&self, f: F) -> T {
|
||||
// `block_on()` panics if called within an asynchronous execution context. Whereas
|
||||
// `block_in_place()` only panics if called from a current_thread runtime, which is the
|
||||
// lesser evil.
|
||||
tokio::task::block_in_place(move || self.runtime.as_ref().expect("runtime").block_on(f))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_instructions(message: &solana_sdk::message::Message) -> Vec<Instruction> {
|
||||
message
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|ci| solana_sdk::instruction::Instruction {
|
||||
program_id: *ci.program_id(&message.account_keys),
|
||||
accounts: ci
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|&index| AccountMeta {
|
||||
pubkey: message.account_keys[index as usize],
|
||||
is_signer: message.is_signer(index.into()),
|
||||
is_writable: message.is_writable(index.into()),
|
||||
})
|
||||
.collect(),
|
||||
data: ci.data.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct Serum3Data<'a> {
|
||||
|
@ -835,3 +1109,19 @@ pub fn pubkey_from_cli(pubkey: &str) -> Pubkey {
|
|||
Err(_) => keypair_from_cli(pubkey).pubkey(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_readonly_account_meta(pubkey: Pubkey) -> AccountMeta {
|
||||
AccountMeta {
|
||||
pubkey,
|
||||
is_writable: false,
|
||||
is_signer: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_writable_account_meta(pubkey: Pubkey) -> AccountMeta {
|
||||
AccountMeta {
|
||||
pubkey,
|
||||
is_writable: true,
|
||||
is_signer: false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ use mango_v4::state::{
|
|||
TokenIndex,
|
||||
};
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::gpa::*;
|
||||
|
||||
use solana_sdk::account::Account;
|
||||
|
@ -16,13 +18,21 @@ use solana_sdk::instruction::AccountMeta;
|
|||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TokenContext {
|
||||
pub token_index: TokenIndex,
|
||||
pub name: String,
|
||||
pub mint_info: MintInfo,
|
||||
pub mint_info_address: Pubkey,
|
||||
pub decimals: u8,
|
||||
}
|
||||
|
||||
impl TokenContext {
|
||||
pub fn native_to_ui(&self, native: I80F48) -> f64 {
|
||||
(native / I80F48::from(10u64.pow(self.decimals.into()))).to_num()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Serum3MarketContext {
|
||||
pub address: Pubkey,
|
||||
pub market: Serum3Market,
|
||||
|
@ -68,6 +78,13 @@ impl MangoGroupContext {
|
|||
self.tokens.get(&token_index).unwrap()
|
||||
}
|
||||
|
||||
pub fn token_by_mint(&self, mint: &Pubkey) -> anyhow::Result<&TokenContext> {
|
||||
self.tokens
|
||||
.iter()
|
||||
.find_map(|(_, tc)| (tc.mint_info.mint == *mint).then(|| tc))
|
||||
.ok_or_else(|| anyhow::anyhow!("no token for mint {}", mint))
|
||||
}
|
||||
|
||||
pub fn perp_market_address(&self, perp_market_index: PerpMarketIndex) -> Pubkey {
|
||||
self.perp_markets.get(&perp_market_index).unwrap().address
|
||||
}
|
||||
|
@ -89,6 +106,7 @@ impl MangoGroupContext {
|
|||
(
|
||||
mi.token_index,
|
||||
TokenContext {
|
||||
token_index: mi.token_index,
|
||||
name: String::new(),
|
||||
mint_info: *mi,
|
||||
mint_info_address: *pk,
|
||||
|
@ -187,7 +205,7 @@ impl MangoGroupContext {
|
|||
pub fn derive_health_check_remaining_account_metas(
|
||||
&self,
|
||||
account: &MangoAccountValue,
|
||||
affected_token: Option<TokenIndex>,
|
||||
affected_tokens: Vec<TokenIndex>,
|
||||
writable_banks: bool,
|
||||
) -> anyhow::Result<Vec<AccountMeta>> {
|
||||
// figure out all the banks/oracles that need to be passed for the health check
|
||||
|
@ -198,7 +216,7 @@ impl MangoGroupContext {
|
|||
banks.push(mint_info.first_bank());
|
||||
oracles.push(mint_info.oracle);
|
||||
}
|
||||
if let Some(affected_token_index) = affected_token {
|
||||
for affected_token_index in affected_tokens {
|
||||
if account
|
||||
.token_iter_active()
|
||||
.find(|p| p.token_index == affected_token_index)
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueryResult {
|
||||
pub data: Vec<QueryRoute>,
|
||||
pub time_taken: f64,
|
||||
pub context_slot: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueryRoute {
|
||||
pub in_amount: u64,
|
||||
pub out_amount: u64,
|
||||
pub amount: u64,
|
||||
pub other_amount_threshold: u64,
|
||||
pub out_amount_with_slippage: u64,
|
||||
pub swap_mode: String,
|
||||
pub price_impact_pct: f64,
|
||||
pub market_infos: Vec<QueryMarketInfo>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueryMarketInfo {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub input_mint: String,
|
||||
pub output_mint: String,
|
||||
pub in_amount: u64,
|
||||
pub out_amount: u64,
|
||||
pub lp_fee: QueryFee,
|
||||
pub platform_fee: QueryFee,
|
||||
pub not_enough_liquidity: bool,
|
||||
pub price_impact_pct: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueryFee {
|
||||
pub amount: u64,
|
||||
pub mint: String,
|
||||
pub pct: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SwapRequest {
|
||||
pub route: QueryRoute,
|
||||
pub user_public_key: String,
|
||||
pub wrap_unwrap_sol: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SwapResponse {
|
||||
pub setup_transaction: Option<String>,
|
||||
pub swap_transaction: String,
|
||||
pub cleanup_transaction: Option<String>,
|
||||
}
|
|
@ -9,4 +9,5 @@ mod chain_data_fetcher;
|
|||
mod client;
|
||||
mod context;
|
||||
mod gpa;
|
||||
mod jupiter;
|
||||
mod util;
|
||||
|
|
|
@ -70,6 +70,7 @@ pub async fn loop_update_index_and_rate(mango_client: Arc<MangoClient>, token_in
|
|||
program_id: mango_v4::id(),
|
||||
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::TokenUpdateIndexAndRate {
|
||||
group: token.mint_info.group,
|
||||
mint_info: token.mint_info_address,
|
||||
oracle,
|
||||
instructions: solana_program::sysvar::instructions::id(),
|
||||
|
@ -240,6 +241,7 @@ pub async fn loop_update_funding(
|
|||
program_id: mango_v4::id(),
|
||||
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::PerpUpdateFunding {
|
||||
group: perp_market.group,
|
||||
perp_market: pk,
|
||||
asks: perp_market.asks,
|
||||
bids: perp_market.bids,
|
||||
|
|
|
@ -2,6 +2,7 @@ mod crank;
|
|||
mod taker;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anchor_client::Cluster;
|
||||
|
||||
|
@ -75,7 +76,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
};
|
||||
|
||||
let mango_client = Arc::new(MangoClient::new_for_existing_account(
|
||||
Client::new(cluster, commitment, &owner),
|
||||
Client::new(cluster, commitment, &owner, Some(Duration::from_secs(1))),
|
||||
cli.mango_account,
|
||||
owner,
|
||||
)?);
|
||||
|
|
|
@ -114,7 +114,7 @@ fn ensure_deposit(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error>
|
|||
}
|
||||
|
||||
log::info!("Depositing {} {}", deposit_native, bank.name());
|
||||
mango_client.token_deposit(bank.name(), desired_balance.to_num())?;
|
||||
mango_client.token_deposit(bank.mint, desired_balance.to_num())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -16,7 +16,7 @@ pub fn new_health_cache_(
|
|||
let active_token_len = account.token_iter_active().count();
|
||||
let active_perp_len = account.perp_iter_active_accounts().count();
|
||||
|
||||
let metas = context.derive_health_check_remaining_account_metas(account, None, false)?;
|
||||
let metas = context.derive_health_check_remaining_account_metas(account, vec![], false)?;
|
||||
let accounts = metas
|
||||
.iter()
|
||||
.map(|meta| {
|
||||
|
|
|
@ -99,14 +99,14 @@ async fn main() -> anyhow::Result<()> {
|
|||
let rpc_timeout = Duration::from_secs(1);
|
||||
let cluster = Cluster::Custom(rpc_url.clone(), ws_url.clone());
|
||||
let commitment = CommitmentConfig::processed();
|
||||
let client = Client::new(cluster.clone(), commitment, &liqor_owner);
|
||||
let client = Client::new(cluster.clone(), commitment, &liqor_owner, Some(rpc_timeout));
|
||||
|
||||
// The representation of current on-chain account data
|
||||
let chain_data = Arc::new(RwLock::new(chain_data::ChainData::new()));
|
||||
// Reading accounts from chain_data
|
||||
let account_fetcher = Arc::new(chain_data::AccountFetcher {
|
||||
chain_data: chain_data.clone(),
|
||||
rpc: client.rpc_with_timeout(rpc_timeout),
|
||||
rpc: client.rpc(),
|
||||
});
|
||||
|
||||
let mango_account = account_fetcher.fetch_fresh_mango_account(&cli.liqor_mango_account)?;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
@ -23,13 +23,13 @@
|
|||
"example1-user": "ts-node ts/client/src/scripts/example1-user.ts",
|
||||
"example1-admin": "ts-node ts/client/src/scripts/example1-admin.ts",
|
||||
"scratch": "ts-node ts/client/src/scripts/scratch/scratch.ts",
|
||||
"format": "prettier --check .",
|
||||
"lint": "eslint . --ext ts --ext tsx --ext js --quiet",
|
||||
"format": "prettier --check ./ts",
|
||||
"lint": "eslint ./ts --ext ts --ext tsx --ext js --quiet",
|
||||
"typecheck": "tsc --noEmit --pretty",
|
||||
"prepare": "yarn build",
|
||||
"prebuild": "npm run clean",
|
||||
"prepublishOnly": "npm run validate && npm run build",
|
||||
"validate": "npm run typecheck && npm run test && npm run lint && npm run format-check"
|
||||
"validate": "npm run typecheck && npm run lint && npm run format"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jup-ag/core": "^1.0.0-beta.28",
|
||||
|
@ -38,8 +38,8 @@
|
|||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/node": "^14.14.37",
|
||||
"@typescript-eslint/eslint-plugin": "^4.14.2",
|
||||
"@typescript-eslint/parser": "^4.14.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.32.0",
|
||||
"@typescript-eslint/parser": "^5.32.0",
|
||||
"chai": "^4.3.4",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
|
|
|
@ -8,7 +8,12 @@ use crate::state::*;
|
|||
pub struct AccountClose<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group, has_one = owner, close = sol_destination)]
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
has_one = owner,
|
||||
close = sol_destination
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::state::{
|
|||
use crate::util::checked_math as cm;
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
|
||||
use anchor_lang::Discriminator;
|
||||
use anchor_spl::token::{self, Token, TokenAccount};
|
||||
use fixed::types::I80F48;
|
||||
|
||||
|
@ -76,19 +77,20 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>(
|
|||
require_keys_eq!(bank.group, ctx.accounts.group.key());
|
||||
require_keys_eq!(bank.vault, *vault_ai.key);
|
||||
|
||||
let vault = Account::<TokenAccount>::try_from(vault_ai)?;
|
||||
let token_account = Account::<TokenAccount>::try_from(token_account_ai)?;
|
||||
|
||||
bank.flash_loan_approved_amount = *amount;
|
||||
bank.flash_loan_vault_initial = token_account.amount;
|
||||
bank.flash_loan_token_account_initial = token_account.amount;
|
||||
|
||||
// Transfer the loaned funds
|
||||
if *amount > 0 {
|
||||
// Provide a readable error message in case the vault doesn't have enough tokens
|
||||
if token_account.amount < *amount {
|
||||
if vault.amount < *amount {
|
||||
return err!(MangoError::InsufficentBankVaultFunds).with_context(|| {
|
||||
format!(
|
||||
"bank vault {} does not have enough tokens, need {} but have {}",
|
||||
vault_ai.key, amount, token_account.amount
|
||||
vault_ai.key, amount, vault.amount
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -141,11 +143,11 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>(
|
|||
|
||||
// must be the FlashLoanEnd instruction
|
||||
require!(
|
||||
ix.data[0..8] == [178, 170, 2, 78, 240, 23, 190, 178],
|
||||
ix.data[0..8] == crate::instruction::FlashLoanEnd::discriminator(),
|
||||
MangoError::SomeError
|
||||
);
|
||||
|
||||
// check that the same vaults are passed
|
||||
// check that the same vaults and token accounts are passed
|
||||
let begin_accounts = &ctx.remaining_accounts[num_loans..];
|
||||
let end_accounts = &ix.accounts[ix.accounts.len() - 2 * num_loans..];
|
||||
for (begin_account, end_account) in begin_accounts.iter().zip(end_accounts.iter()) {
|
||||
|
@ -180,6 +182,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let group = account.fixed.group;
|
||||
|
||||
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
|
@ -233,7 +236,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
// The Begin instruction only checks that End ends with the same vault accounts -
|
||||
// but there could be an extra vault account in End, or a different bank could be
|
||||
// used for the same vault.
|
||||
require_neq!(bank.flash_loan_vault_initial, u64::MAX);
|
||||
require_neq!(bank.flash_loan_token_account_initial, u64::MAX);
|
||||
|
||||
// Create the token position now, so we can compute the pre-health with fixed order health accounts
|
||||
let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?;
|
||||
|
@ -241,7 +244,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
// Transfer any excess over the inital balance of the token account back
|
||||
// into the vault. Compute the total change in the vault balance.
|
||||
let mut change = -I80F48::from(bank.flash_loan_approved_amount);
|
||||
if token_account.amount > bank.flash_loan_vault_initial {
|
||||
if token_account.amount > bank.flash_loan_token_account_initial {
|
||||
let transfer_ctx = CpiContext::new(
|
||||
ctx.accounts.token_program.to_account_info(),
|
||||
token::Transfer {
|
||||
|
@ -250,7 +253,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
authority: ctx.accounts.owner.to_account_info(),
|
||||
},
|
||||
);
|
||||
let repay = token_account.amount - bank.flash_loan_vault_initial;
|
||||
let repay = token_account.amount - bank.flash_loan_token_account_initial;
|
||||
token::transfer(transfer_ctx, repay)?;
|
||||
|
||||
let repay = I80F48::from(repay);
|
||||
|
@ -322,7 +325,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
}
|
||||
|
||||
bank.flash_loan_approved_amount = 0;
|
||||
bank.flash_loan_vault_initial = u64::MAX;
|
||||
bank.flash_loan_token_account_initial = u64::MAX;
|
||||
|
||||
token_loan_details.push(FlashLoanTokenDetail {
|
||||
token_index: position.token_index,
|
||||
|
@ -335,6 +338,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
});
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
token_index: bank.token_index as u16,
|
||||
indexed_position: position.indexed_position.to_bits(),
|
||||
|
@ -345,6 +349,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
}
|
||||
|
||||
emit!(FlashLoanLog {
|
||||
mango_group: group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
token_loan_details
|
||||
});
|
||||
|
|
|
@ -157,6 +157,7 @@ pub fn liq_token_with_token(
|
|||
);
|
||||
|
||||
emit!(LiquidateTokenAndTokenLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
liqee: ctx.accounts.liqee.key(),
|
||||
liqor: ctx.accounts.liqor.key(),
|
||||
asset_token_index,
|
||||
|
@ -170,6 +171,7 @@ pub fn liq_token_with_token(
|
|||
|
||||
// liqee asset
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.liqee.key(),
|
||||
token_index: asset_token_index,
|
||||
indexed_position: liqee_asset_position_indexed.to_bits(),
|
||||
|
@ -179,6 +181,7 @@ pub fn liq_token_with_token(
|
|||
});
|
||||
// liqee liab
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.liqee.key(),
|
||||
token_index: liab_token_index,
|
||||
indexed_position: liqee_liab_position_indexed.to_bits(),
|
||||
|
@ -188,6 +191,7 @@ pub fn liq_token_with_token(
|
|||
});
|
||||
// liqor asset
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.liqor.key(),
|
||||
token_index: asset_token_index,
|
||||
indexed_position: liqor_asset_position_indexed.to_bits(),
|
||||
|
@ -197,6 +201,7 @@ pub fn liq_token_with_token(
|
|||
});
|
||||
// liqor liab
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.liqor.key(),
|
||||
token_index: liab_token_index,
|
||||
indexed_position: liqor_liab_position_indexed.to_bits(),
|
||||
|
|
|
@ -61,6 +61,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
fill,
|
||||
)?;
|
||||
emit_perp_balances(
|
||||
ctx.accounts.group.key(),
|
||||
fill.maker,
|
||||
perp_market.perp_market_index as u64,
|
||||
fill.price,
|
||||
|
@ -101,6 +102,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
fill,
|
||||
)?;
|
||||
emit_perp_balances(
|
||||
ctx.accounts.group.key(),
|
||||
fill.maker,
|
||||
perp_market.perp_market_index as u64,
|
||||
fill.price,
|
||||
|
@ -110,6 +112,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
&perp_market,
|
||||
);
|
||||
emit_perp_balances(
|
||||
ctx.accounts.group.key(),
|
||||
fill.taker,
|
||||
perp_market.perp_market_index as u64,
|
||||
fill.price,
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::state::{oracle_price, Book, BookSide, PerpMarket};
|
||||
use crate::state::{oracle_price, Book, BookSide, Group, PerpMarket};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PerpUpdateFunding<'info> {
|
||||
pub group: AccountLoader<'info, Group>, // Required for group metadata parsing
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = bids,
|
||||
has_one = asks,
|
||||
has_one = oracle,
|
||||
constraint = perp_market.load()?.group.key() == group.key(),
|
||||
)]
|
||||
pub perp_market: AccountLoader<'info, PerpMarket>,
|
||||
#[account(mut)]
|
||||
|
|
|
@ -83,6 +83,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
|||
cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
token_index,
|
||||
indexed_position: indexed_position.to_bits(),
|
||||
|
@ -111,6 +112,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
|||
}
|
||||
|
||||
emit!(DepositLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
signer: ctx.accounts.token_authority.key(),
|
||||
token_index,
|
||||
|
|
|
@ -109,7 +109,7 @@ pub fn token_edit(
|
|||
|
||||
// unchanged -
|
||||
// dust
|
||||
// flash_loan_vault_initial
|
||||
// flash_loan_token_account_initial
|
||||
// flash_loan_approved_amount
|
||||
// token_index
|
||||
// bump
|
||||
|
|
|
@ -131,7 +131,7 @@ pub fn token_register(
|
|||
init_liab_weight: I80F48::from_num(init_liab_weight),
|
||||
liquidation_fee: I80F48::from_num(liquidation_fee),
|
||||
dust: I80F48::ZERO,
|
||||
flash_loan_vault_initial: u64::MAX,
|
||||
flash_loan_token_account_initial: u64::MAX,
|
||||
flash_loan_approved_amount: 0,
|
||||
token_index,
|
||||
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,
|
||||
|
|
|
@ -103,7 +103,7 @@ pub fn token_register_trustless(
|
|||
init_liab_weight: I80F48::from_num(1.5),
|
||||
liquidation_fee: I80F48::from_num(0.125),
|
||||
dust: I80F48::ZERO,
|
||||
flash_loan_vault_initial: u64::MAX,
|
||||
flash_loan_token_account_initial: u64::MAX,
|
||||
flash_loan_approved_amount: 0,
|
||||
token_index,
|
||||
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,
|
||||
|
|
|
@ -5,16 +5,20 @@ use crate::logs::{UpdateIndexLog, UpdateRateLog};
|
|||
use crate::state::HOUR;
|
||||
use crate::{
|
||||
accounts_zerocopy::{AccountInfoRef, LoadMutZeroCopyRef, LoadZeroCopyRef},
|
||||
state::{oracle_price, Bank, MintInfo},
|
||||
state::{oracle_price, Bank, Group, MintInfo},
|
||||
};
|
||||
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
|
||||
use anchor_lang::Discriminator;
|
||||
use checked_math as cm;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TokenUpdateIndexAndRate<'info> {
|
||||
pub group: AccountLoader<'info, Group>, // Required for group metadata parsing
|
||||
|
||||
#[account(
|
||||
has_one = oracle
|
||||
has_one = oracle,
|
||||
constraint = mint_info.load()?.group.key() == group.key(),
|
||||
)]
|
||||
pub mint_info: AccountLoader<'info, MintInfo>,
|
||||
|
||||
|
@ -43,7 +47,8 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
|
|||
// 2. we want to forbid cpi, since ix we would like to blacklist could just be called from cpi
|
||||
require!(
|
||||
ix.program_id == crate::id()
|
||||
&& ix.data[0..8] == [131, 136, 194, 39, 11, 50, 10, 198], // token_update_index_and_rate
|
||||
&& ix.data[0..8]
|
||||
== crate::instruction::TokenUpdateIndexAndRate::discriminator(),
|
||||
MangoError::SomeError
|
||||
);
|
||||
|
||||
|
|
|
@ -125,6 +125,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
token_index,
|
||||
indexed_position: indexed_position.to_bits(),
|
||||
|
@ -151,6 +152,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
}
|
||||
|
||||
emit!(WithdrawLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
signer: ctx.accounts.owner.key(),
|
||||
token_index,
|
||||
|
|
|
@ -4,6 +4,7 @@ use borsh::BorshSerialize;
|
|||
|
||||
/// Warning: This function needs 512+ bytes free on the stack
|
||||
pub fn emit_perp_balances(
|
||||
mango_group: Pubkey,
|
||||
mango_account: Pubkey,
|
||||
market_index: u64,
|
||||
price: i64,
|
||||
|
@ -11,6 +12,7 @@ pub fn emit_perp_balances(
|
|||
pm: &PerpMarket,
|
||||
) {
|
||||
emit!(PerpBalanceLog {
|
||||
mango_group,
|
||||
mango_account,
|
||||
market_index,
|
||||
base_position: pp.base_position_lots,
|
||||
|
@ -25,6 +27,7 @@ pub fn emit_perp_balances(
|
|||
|
||||
#[event]
|
||||
pub struct PerpBalanceLog {
|
||||
pub mango_group: Pubkey,
|
||||
pub mango_account: Pubkey,
|
||||
pub market_index: u64, // IDL doesn't support usize
|
||||
pub base_position: i64,
|
||||
|
@ -38,6 +41,7 @@ pub struct PerpBalanceLog {
|
|||
|
||||
#[event]
|
||||
pub struct TokenBalanceLog {
|
||||
pub mango_group: Pubkey,
|
||||
pub mango_account: Pubkey,
|
||||
pub token_index: u16, // IDL doesn't support usize
|
||||
pub indexed_position: i128, // on client convert i128 to I80F48 easily by passing in the BN to I80F48 ctor
|
||||
|
@ -46,14 +50,6 @@ pub struct TokenBalanceLog {
|
|||
pub price: i128, // I80F48
|
||||
}
|
||||
|
||||
#[event]
|
||||
pub struct MarginTradeLog {
|
||||
pub mango_account: Pubkey,
|
||||
pub token_indexes: Vec<u16>,
|
||||
pub pre_indexed_positions: Vec<i128>,
|
||||
pub post_indexed_positions: Vec<i128>,
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct FlashLoanTokenDetail {
|
||||
pub token_index: u16,
|
||||
|
@ -67,12 +63,14 @@ pub struct FlashLoanTokenDetail {
|
|||
|
||||
#[event]
|
||||
pub struct FlashLoanLog {
|
||||
pub mango_group: Pubkey,
|
||||
pub mango_account: Pubkey,
|
||||
pub token_loan_details: Vec<FlashLoanTokenDetail>,
|
||||
}
|
||||
|
||||
#[event]
|
||||
pub struct WithdrawLog {
|
||||
pub mango_group: Pubkey,
|
||||
pub mango_account: Pubkey,
|
||||
pub signer: Pubkey,
|
||||
pub token_index: u16,
|
||||
|
@ -82,6 +80,7 @@ pub struct WithdrawLog {
|
|||
|
||||
#[event]
|
||||
pub struct DepositLog {
|
||||
pub mango_group: Pubkey,
|
||||
pub mango_account: Pubkey,
|
||||
pub signer: Pubkey,
|
||||
pub token_index: u16,
|
||||
|
@ -147,6 +146,7 @@ pub struct UpdateRateLog {
|
|||
|
||||
#[event]
|
||||
pub struct LiquidateTokenAndTokenLog {
|
||||
pub mango_group: Pubkey,
|
||||
pub liqee: Pubkey,
|
||||
pub liqor: Pubkey,
|
||||
pub asset_token_index: u16,
|
||||
|
|
|
@ -82,7 +82,7 @@ pub struct Bank {
|
|||
// Collection of all fractions-of-native-tokens that got rounded away
|
||||
pub dust: I80F48,
|
||||
|
||||
pub flash_loan_vault_initial: u64,
|
||||
pub flash_loan_token_account_initial: u64,
|
||||
pub flash_loan_approved_amount: u64,
|
||||
|
||||
// Index into TokenInfo on the group
|
||||
|
@ -145,7 +145,10 @@ impl std::fmt::Debug for Bank {
|
|||
"flash_loan_approved_amount",
|
||||
&self.flash_loan_approved_amount,
|
||||
)
|
||||
.field("flash_loan_vault_initial", &self.flash_loan_vault_initial)
|
||||
.field(
|
||||
"flash_loan_token_account_initial",
|
||||
&self.flash_loan_token_account_initial,
|
||||
)
|
||||
.field("reserved", &self.reserved)
|
||||
.finish()
|
||||
}
|
||||
|
@ -185,7 +188,7 @@ impl Bank {
|
|||
liquidation_fee: existing_bank.liquidation_fee,
|
||||
dust: I80F48::ZERO,
|
||||
flash_loan_approved_amount: 0,
|
||||
flash_loan_vault_initial: u64::MAX,
|
||||
flash_loan_token_account_initial: u64::MAX,
|
||||
token_index: existing_bank.token_index,
|
||||
bump: existing_bank.bump,
|
||||
mint_decimals: existing_bank.mint_decimals,
|
||||
|
|
|
@ -2374,6 +2374,7 @@ impl ClientInstruction for PerpConsumeEventsInstruction {
|
|||
}
|
||||
|
||||
pub struct PerpUpdateFundingInstruction {
|
||||
pub group: Pubkey,
|
||||
pub perp_market: Pubkey,
|
||||
pub bids: Pubkey,
|
||||
pub asks: Pubkey,
|
||||
|
@ -2391,6 +2392,7 @@ impl ClientInstruction for PerpUpdateFundingInstruction {
|
|||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {};
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
perp_market: self.perp_market,
|
||||
bids: self.bids,
|
||||
asks: self.asks,
|
||||
|
@ -2444,6 +2446,7 @@ impl ClientInstruction for TokenUpdateIndexAndRateInstruction {
|
|||
let mint_info: MintInfo = loader.load(&self.mint_info).await.unwrap();
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: mint_info.group,
|
||||
mint_info: self.mint_info,
|
||||
oracle: mint_info.oracle,
|
||||
instructions: solana_program::sysvar::instructions::id(),
|
||||
|
|
|
@ -106,4 +106,4 @@
|
|||
"perpMarkets": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ export class I80F48 {
|
|||
this.data = data;
|
||||
}
|
||||
static fromNumber(x: number): I80F48 {
|
||||
let int_part = Math.trunc(x);
|
||||
let v = new BN(int_part).iushln(48);
|
||||
const int_part = Math.trunc(x);
|
||||
const v = new BN(int_part).iushln(48);
|
||||
v.iadd(new BN((x - int_part) * I80F48.MULTIPLIER_NUMBER));
|
||||
return new I80F48(v);
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ export class Bank {
|
|||
initLiabWeight: I80F48Dto;
|
||||
liquidationFee: I80F48Dto;
|
||||
dust: I80F48Dto;
|
||||
flashLoanVaultInitial: BN;
|
||||
flashLoanTokenAccountInitial: BN;
|
||||
flashLoanApprovedAmount: BN;
|
||||
tokenIndex: number;
|
||||
mintDecimals: number;
|
||||
|
@ -102,7 +102,7 @@ export class Bank {
|
|||
obj.initLiabWeight,
|
||||
obj.liquidationFee,
|
||||
obj.dust,
|
||||
obj.flashLoanVaultInitial,
|
||||
obj.flashLoanTokenAccountInitial,
|
||||
obj.flashLoanApprovedAmount,
|
||||
obj.tokenIndex,
|
||||
obj.mintDecimals,
|
||||
|
@ -141,9 +141,9 @@ export class Bank {
|
|||
maintLiabWeight: I80F48Dto,
|
||||
initLiabWeight: I80F48Dto,
|
||||
liquidationFee: I80F48Dto,
|
||||
dust: Object,
|
||||
flashLoanVaultInitial: Object,
|
||||
flashLoanApprovedAmount: Object,
|
||||
dust: I80F48Dto,
|
||||
flashLoanTokenAccountInitial: BN,
|
||||
flashLoanApprovedAmount: BN,
|
||||
public tokenIndex: number,
|
||||
public mintDecimals: number,
|
||||
public bankNum: number,
|
||||
|
@ -337,7 +337,7 @@ export class MintInfo {
|
|||
}
|
||||
|
||||
toString(): string {
|
||||
let res =
|
||||
const res =
|
||||
'mint ' +
|
||||
this.mint.toBase58() +
|
||||
'\n oracle ' +
|
||||
|
|
|
@ -207,7 +207,7 @@ export class Group {
|
|||
price.data.slice(0, 8),
|
||||
)
|
||||
) {
|
||||
let stubOracle = coder.decode('stubOracle', price.data);
|
||||
const stubOracle = coder.decode('stubOracle', price.data);
|
||||
banks[index].price = new I80F48(stubOracle.price.val);
|
||||
} else {
|
||||
banks[index].price = I80F48.fromNumber(
|
||||
|
|
|
@ -39,15 +39,18 @@ export class HealthCache {
|
|||
public health(healthType: HealthType): I80F48 {
|
||||
let health = ZERO_I80F48;
|
||||
for (const tokenInfo of this.tokenInfos) {
|
||||
let contrib = tokenInfo.healthContribution(healthType);
|
||||
const contrib = tokenInfo.healthContribution(healthType);
|
||||
health = health.add(contrib);
|
||||
}
|
||||
for (const serum3Info of this.serum3Infos) {
|
||||
let contrib = serum3Info.healthContribution(healthType, this.tokenInfos);
|
||||
const contrib = serum3Info.healthContribution(
|
||||
healthType,
|
||||
this.tokenInfos,
|
||||
);
|
||||
health = health.add(contrib);
|
||||
}
|
||||
for (const perpInfo of this.perpInfos) {
|
||||
let contrib = perpInfo.healthContribution(healthType);
|
||||
const contrib = perpInfo.healthContribution(healthType);
|
||||
health = health.add(contrib);
|
||||
}
|
||||
return health;
|
||||
|
@ -110,9 +113,9 @@ export class Serum3Info {
|
|||
quoteIndex: number;
|
||||
|
||||
healthContribution(healthType: HealthType, tokenInfos: TokenInfo[]): I80F48 {
|
||||
let baseInfo = tokenInfos[this.baseIndex];
|
||||
let quoteInfo = tokenInfos[this.quoteIndex];
|
||||
let reserved = this.reserved;
|
||||
const baseInfo = tokenInfos[this.baseIndex];
|
||||
const quoteInfo = tokenInfos[this.quoteIndex];
|
||||
const reserved = this.reserved;
|
||||
|
||||
if (reserved.isZero()) {
|
||||
return ZERO_I80F48;
|
||||
|
@ -120,10 +123,10 @@ export class Serum3Info {
|
|||
|
||||
// How much the health would increase if the reserved balance were applied to the passed
|
||||
// token info?
|
||||
let computeHealthEffect = function (tokenInfo: TokenInfo) {
|
||||
const computeHealthEffect = function (tokenInfo: TokenInfo) {
|
||||
// This balance includes all possible reserved funds from markets that relate to the
|
||||
// token, including this market itself: `reserved` is already included in `max_balance`.
|
||||
let maxBalance = tokenInfo.balance.add(tokenInfo.serum3MaxReserved);
|
||||
const maxBalance = tokenInfo.balance.add(tokenInfo.serum3MaxReserved);
|
||||
|
||||
// Assuming `reserved` was added to `max_balance` last (because that gives the smallest
|
||||
// health effects): how much did health change because of it?
|
||||
|
@ -139,13 +142,13 @@ export class Serum3Info {
|
|||
liabPart = reserved.sub(maxBalance);
|
||||
}
|
||||
|
||||
let assetWeight = tokenInfo.assetWeight(healthType);
|
||||
let liabWeight = tokenInfo.liabWeight(healthType);
|
||||
const assetWeight = tokenInfo.assetWeight(healthType);
|
||||
const liabWeight = tokenInfo.liabWeight(healthType);
|
||||
return assetWeight.mul(assetPart).add(liabWeight.mul(liabPart));
|
||||
};
|
||||
|
||||
let reservedAsBase = computeHealthEffect(baseInfo);
|
||||
let reservedAsQuote = computeHealthEffect(quoteInfo);
|
||||
const reservedAsBase = computeHealthEffect(baseInfo);
|
||||
const reservedAsQuote = computeHealthEffect(quoteInfo);
|
||||
return reservedAsBase.min(reservedAsQuote);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ export class MangoAccount {
|
|||
netSettled: number;
|
||||
headerVersion: number;
|
||||
tokens: unknown;
|
||||
serum3: Object;
|
||||
serum3: unknown;
|
||||
perps: unknown;
|
||||
perpOpenOrders: unknown;
|
||||
},
|
||||
|
@ -50,7 +50,7 @@ export class MangoAccount {
|
|||
obj.serum3 as Serum3PositionDto[],
|
||||
obj.perps as PerpPositionDto[],
|
||||
obj.perpOpenOrders as any,
|
||||
{},
|
||||
{} as any,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ export class MangoAccount {
|
|||
serum3: Serum3PositionDto[],
|
||||
perps: PerpPositionDto[],
|
||||
perpOpenOrders: PerpPositionDto[],
|
||||
public accountData: {},
|
||||
public accountData: MangoAccountData,
|
||||
) {
|
||||
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||
this.tokens = tokens.map((dto) => TokenPosition.from(dto));
|
||||
|
@ -172,7 +172,7 @@ export class MangoAccount {
|
|||
*/
|
||||
getEquity(): I80F48 {
|
||||
const equity = (this.accountData as MangoAccountData).equity;
|
||||
let total_equity = equity.tokens.reduce(
|
||||
const total_equity = equity.tokens.reduce(
|
||||
(a, b) => a.add(b.value),
|
||||
ZERO_I80F48,
|
||||
);
|
||||
|
@ -191,7 +191,7 @@ export class MangoAccount {
|
|||
*/
|
||||
getAssetsVal(): I80F48 {
|
||||
const equity = (this.accountData as MangoAccountData).equity;
|
||||
let total_equity = equity.tokens.reduce(
|
||||
const total_equity = equity.tokens.reduce(
|
||||
(a, b) => (b.value.gt(ZERO_I80F48) ? a.add(b.value) : a),
|
||||
ZERO_I80F48,
|
||||
);
|
||||
|
@ -203,7 +203,7 @@ export class MangoAccount {
|
|||
*/
|
||||
getLiabsVal(): I80F48 {
|
||||
const equity = (this.accountData as MangoAccountData).equity;
|
||||
let total_equity = equity.tokens.reduce(
|
||||
const total_equity = equity.tokens.reduce(
|
||||
(a, b) => (b.value.lt(ZERO_I80F48) ? a.add(b.value) : a),
|
||||
ZERO_I80F48,
|
||||
);
|
||||
|
@ -374,7 +374,7 @@ export class MangoAccount {
|
|||
}
|
||||
|
||||
export class TokenPosition {
|
||||
static TokenIndexUnset: number = 65535;
|
||||
static TokenIndexUnset = 65535;
|
||||
static from(dto: TokenPositionDto) {
|
||||
return new TokenPosition(
|
||||
I80F48.from(dto.indexedPosition),
|
||||
|
@ -419,12 +419,12 @@ export class TokenPosition {
|
|||
).toNumber();
|
||||
}
|
||||
|
||||
public toString(group?: Group): String {
|
||||
let extra: string = '';
|
||||
public toString(group?: Group): string {
|
||||
let extra = '';
|
||||
if (group) {
|
||||
let bank = group.findBank(this.tokenIndex);
|
||||
const bank = group.findBank(this.tokenIndex);
|
||||
if (bank) {
|
||||
let native = this.native(bank);
|
||||
const native = this.native(bank);
|
||||
extra += ', native: ' + native.toNumber();
|
||||
extra += ', ui: ' + this.ui(bank);
|
||||
extra += ', tokenName: ' + bank.name;
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
AnchorProvider,
|
||||
BN,
|
||||
Program,
|
||||
Provider,
|
||||
web3,
|
||||
} from '@project-serum/anchor';
|
||||
import { AnchorProvider, BN, Program, Provider } from '@project-serum/anchor';
|
||||
import { getFeeRates, getFeeTier } from '@project-serum/serum';
|
||||
import { Order } from '@project-serum/serum/lib/market';
|
||||
import {
|
||||
|
@ -472,20 +466,13 @@ export class MangoClient {
|
|||
public async getOrCreateMangoAccount(
|
||||
group: Group,
|
||||
ownerPk: PublicKey,
|
||||
payer: web3.Keypair,
|
||||
accountNumber?: number,
|
||||
accountSize?: AccountSize,
|
||||
name?: string,
|
||||
): Promise<MangoAccount> {
|
||||
let mangoAccounts = await this.getMangoAccountsForOwner(group, ownerPk);
|
||||
if (mangoAccounts.length === 0) {
|
||||
await this.createMangoAccount(
|
||||
group,
|
||||
payer,
|
||||
accountNumber ?? 0,
|
||||
accountSize ?? AccountSize.small,
|
||||
name ?? '',
|
||||
);
|
||||
await this.createMangoAccount(group, accountNumber, accountSize, name);
|
||||
mangoAccounts = await this.getMangoAccountsForOwner(group, ownerPk);
|
||||
}
|
||||
return mangoAccounts[0];
|
||||
|
@ -493,19 +480,21 @@ export class MangoClient {
|
|||
|
||||
public async createMangoAccount(
|
||||
group: Group,
|
||||
payer: web3.Keypair,
|
||||
accountNumber: number,
|
||||
accountSize: AccountSize,
|
||||
accountNumber?: number,
|
||||
accountSize?: AccountSize,
|
||||
name?: string,
|
||||
): Promise<TransactionSignature> {
|
||||
return await this.program.methods
|
||||
.accountCreate(accountNumber, accountSize, name ?? '')
|
||||
.accountCreate(
|
||||
accountNumber ?? 0,
|
||||
accountSize ?? AccountSize.small,
|
||||
name ?? '',
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||
payer: payer.publicKey,
|
||||
payer: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||
})
|
||||
.signers([payer])
|
||||
.rpc();
|
||||
}
|
||||
|
||||
|
@ -631,7 +620,6 @@ export class MangoClient {
|
|||
mangoAccount: MangoAccount,
|
||||
tokenName: string,
|
||||
amount: number,
|
||||
signer: Signer,
|
||||
) {
|
||||
const bank = group.banksMap.get(tokenName)!;
|
||||
|
||||
|
@ -643,7 +631,7 @@ export class MangoClient {
|
|||
let wrappedSolAccount: Keypair | undefined;
|
||||
let preInstructions: TransactionInstruction[] = [];
|
||||
let postInstructions: TransactionInstruction[] = [];
|
||||
let additionalSigners: Signer[] = [];
|
||||
const additionalSigners: Signer[] = [];
|
||||
if (bank.mint.equals(WRAPPED_SOL_MINT)) {
|
||||
wrappedSolAccount = new Keypair();
|
||||
const lamports = Math.round(amount * LAMPORTS_PER_SOL) + 1e7;
|
||||
|
@ -698,7 +686,7 @@ export class MangoClient {
|
|||
)
|
||||
.preInstructions(preInstructions)
|
||||
.postInstructions(postInstructions)
|
||||
.signers([signer].concat(additionalSigners))
|
||||
.signers(additionalSigners)
|
||||
.rpc({ skipPreflight: true });
|
||||
}
|
||||
|
||||
|
@ -708,7 +696,6 @@ export class MangoClient {
|
|||
tokenName: string,
|
||||
amount: number,
|
||||
allowBorrow: boolean,
|
||||
signer: Signer,
|
||||
) {
|
||||
const bank = group.banksMap.get(tokenName)!;
|
||||
|
||||
|
@ -741,7 +728,6 @@ export class MangoClient {
|
|||
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
|
||||
),
|
||||
)
|
||||
.signers([signer])
|
||||
.rpc({ skipPreflight: true });
|
||||
}
|
||||
|
||||
|
@ -751,7 +737,6 @@ export class MangoClient {
|
|||
tokenName: string,
|
||||
nativeAmount: number,
|
||||
allowBorrow: boolean,
|
||||
signer: Signer,
|
||||
) {
|
||||
const bank = group.banksMap.get(tokenName)!;
|
||||
|
||||
|
@ -784,7 +769,6 @@ export class MangoClient {
|
|||
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
|
||||
),
|
||||
)
|
||||
.signers([signer])
|
||||
.rpc({ skipPreflight: true });
|
||||
}
|
||||
|
||||
|
@ -901,7 +885,7 @@ export class MangoClient {
|
|||
): Promise<TransactionSignature> {
|
||||
const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!;
|
||||
|
||||
let openOrders = mangoAccount.serum3.find(
|
||||
const openOrders = mangoAccount.serum3.find(
|
||||
(account) => account.marketIndex === serum3Market.marketIndex,
|
||||
)?.openOrders;
|
||||
|
||||
|
@ -1358,7 +1342,7 @@ export class MangoClient {
|
|||
mangoAccount,
|
||||
]);
|
||||
|
||||
let [nativePrice, nativeQuantity] = perpMarket.uiToNativePriceQuantity(
|
||||
const [nativePrice, nativeQuantity] = perpMarket.uiToNativePriceQuantity(
|
||||
price,
|
||||
quantity,
|
||||
);
|
||||
|
@ -1437,7 +1421,7 @@ export class MangoClient {
|
|||
/*
|
||||
* Find or create associated token accounts
|
||||
*/
|
||||
let inputTokenAccountPk = await getAssociatedTokenAddress(
|
||||
const inputTokenAccountPk = await getAssociatedTokenAddress(
|
||||
inputBank.mint,
|
||||
mangoAccount.owner,
|
||||
);
|
||||
|
@ -1445,7 +1429,7 @@ export class MangoClient {
|
|||
await this.program.provider.connection.getAccountInfo(
|
||||
inputTokenAccountPk,
|
||||
);
|
||||
let preInstructions = [];
|
||||
const preInstructions = [];
|
||||
if (!inputTokenAccExists) {
|
||||
preInstructions.push(
|
||||
Token.createAssociatedTokenAccountInstruction(
|
||||
|
@ -1459,7 +1443,7 @@ export class MangoClient {
|
|||
);
|
||||
}
|
||||
|
||||
let outputTokenAccountPk = await getAssociatedTokenAddress(
|
||||
const outputTokenAccountPk = await getAssociatedTokenAddress(
|
||||
outputBank.mint,
|
||||
mangoAccount.owner,
|
||||
);
|
||||
|
@ -1575,19 +1559,39 @@ export class MangoClient {
|
|||
return this.program.provider.sendAndConfirm(tx);
|
||||
}
|
||||
|
||||
async updateIndexAndRate(group: Group, tokenName: string) {
|
||||
|
||||
let bank = group.banksMap.get(tokenName)!;
|
||||
let mintInfo = group.mintInfosMap.get(bank.tokenIndex)!;
|
||||
|
||||
await this.program.methods.tokenUpdateIndexAndRate().accounts({
|
||||
'group': group.publicKey,
|
||||
'mintInfo': mintInfo.publicKey,
|
||||
'oracle': mintInfo.oracle,
|
||||
'instructions': SYSVAR_INSTRUCTIONS_PUBKEY})
|
||||
.remainingAccounts([
|
||||
{
|
||||
pubkey: bank.publicKey,
|
||||
isWritable: true,
|
||||
isSigner: false,
|
||||
} as AccountMeta,
|
||||
])
|
||||
.rpc()
|
||||
}
|
||||
|
||||
|
||||
/// liquidations
|
||||
|
||||
async liqTokenWithToken(
|
||||
group: Group,
|
||||
liqor: MangoAccount,
|
||||
liqee: MangoAccount,
|
||||
liqorOwner: Signer,
|
||||
assetTokenName: string,
|
||||
liabTokenName: string,
|
||||
maxLiabTransfer: number,
|
||||
) {
|
||||
let assetBank: Bank = group.banksMap.get(assetTokenName);
|
||||
let liabBank: Bank = group.banksMap.get(liabTokenName);
|
||||
const assetBank: Bank = group.banksMap.get(assetTokenName);
|
||||
const liabBank: Bank = group.banksMap.get(liabTokenName);
|
||||
|
||||
const healthRemainingAccounts: PublicKey[] =
|
||||
this.buildHealthRemainingAccounts(
|
||||
|
@ -1617,10 +1621,9 @@ export class MangoClient {
|
|||
group: group.publicKey,
|
||||
liqor: liqor.publicKey,
|
||||
liqee: liqee.publicKey,
|
||||
liqorOwner: liqorOwner.publicKey,
|
||||
liqorOwner: liqor.owner,
|
||||
})
|
||||
.remainingAccounts(parsedHealthAccounts)
|
||||
.signers([liqorOwner])
|
||||
.rpc();
|
||||
}
|
||||
|
||||
|
@ -1634,7 +1637,7 @@ export class MangoClient {
|
|||
// TODO: use IDL on chain or in repository? decide...
|
||||
// Alternatively we could fetch IDL from chain.
|
||||
// const idl = await Program.fetchIdl(MANGO_V4_ID, provider);
|
||||
let idl = IDL;
|
||||
const idl = IDL;
|
||||
|
||||
return new MangoClient(
|
||||
new Program<MangoV4>(idl as MangoV4, programId, provider),
|
||||
|
@ -1650,7 +1653,7 @@ export class MangoClient {
|
|||
// TODO: use IDL on chain or in repository? decide...
|
||||
// Alternatively we could fetch IDL from chain.
|
||||
// const idl = await Program.fetchIdl(MANGO_V4_ID, provider);
|
||||
let idl = IDL;
|
||||
const idl = IDL;
|
||||
|
||||
const id = Id.fromIds(groupName);
|
||||
|
||||
|
@ -1706,7 +1709,7 @@ export class MangoClient {
|
|||
}
|
||||
}
|
||||
|
||||
const mintInfos = [...new Set(tokenIndices.sort())].map(
|
||||
const mintInfos = [...new Set(tokenIndices)].map(
|
||||
(tokenIndex) => group.mintInfosMap.get(tokenIndex)!,
|
||||
);
|
||||
healthRemainingAccounts.push(
|
||||
|
@ -1742,7 +1745,7 @@ export class MangoClient {
|
|||
const healthRemainingAccounts: PublicKey[] = [];
|
||||
|
||||
let tokenIndices = [];
|
||||
for (let mangoAccount of mangoAccounts) {
|
||||
for (const mangoAccount of mangoAccounts) {
|
||||
tokenIndices.push(
|
||||
...mangoAccount.tokens
|
||||
.filter((token) => token.tokenIndex !== 65535)
|
||||
|
@ -1765,14 +1768,14 @@ export class MangoClient {
|
|||
healthRemainingAccounts.push(
|
||||
...mintInfos.map((mintInfo) => mintInfo.oracle),
|
||||
);
|
||||
for (let mangoAccount of mangoAccounts) {
|
||||
for (const mangoAccount of mangoAccounts) {
|
||||
healthRemainingAccounts.push(
|
||||
...mangoAccount.serum3
|
||||
.filter((serum3Account) => serum3Account.marketIndex !== 65535)
|
||||
.map((serum3Account) => serum3Account.openOrders),
|
||||
);
|
||||
}
|
||||
for (let mangoAccount of mangoAccounts) {
|
||||
for (const mangoAccount of mangoAccounts) {
|
||||
healthRemainingAccounts.push(
|
||||
...mangoAccount.perps
|
||||
.filter((perp) => perp.marketIndex !== 65535)
|
||||
|
|
|
@ -50,7 +50,7 @@ export class Id {
|
|||
}
|
||||
|
||||
static fromIds(name: string): Id {
|
||||
let groupConfig = ids.groups.find((id) => id['name'] === name);
|
||||
const groupConfig = ids.groups.find((id) => id['name'] === name);
|
||||
return new Id(
|
||||
groupConfig.cluster as Cluster,
|
||||
name,
|
||||
|
|
|
@ -738,6 +738,11 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "tokenUpdateIndexAndRate",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "mintInfo",
|
||||
"isMut": false,
|
||||
|
@ -2572,6 +2577,11 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "perpUpdateFunding",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "perpMarket",
|
||||
"isMut": true,
|
||||
|
@ -2820,7 +2830,7 @@ export type MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "flashLoanVaultInitial",
|
||||
"name": "flashLoanTokenAccountInitial",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
|
@ -4440,6 +4450,11 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "PerpBalanceLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -4490,6 +4505,11 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "TokenBalanceLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -4523,39 +4543,13 @@ export type MangoV4 = {
|
|||
]
|
||||
},
|
||||
{
|
||||
"name": "MarginTradeLog",
|
||||
"name": "FlashLoanLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "tokenIndexes",
|
||||
"type": {
|
||||
"vec": "u16"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "preIndexedPositions",
|
||||
"type": {
|
||||
"vec": "i128"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "postIndexedPositions",
|
||||
"type": {
|
||||
"vec": "i128"
|
||||
},
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FlashLoanLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -4575,6 +4569,11 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "WithdrawLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -4605,6 +4604,11 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "DepositLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -4830,6 +4834,11 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "LiquidateTokenAndTokenLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "liqee",
|
||||
"type": "publicKey",
|
||||
|
@ -5742,6 +5751,11 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "tokenUpdateIndexAndRate",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "mintInfo",
|
||||
"isMut": false,
|
||||
|
@ -7576,6 +7590,11 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "perpUpdateFunding",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "perpMarket",
|
||||
"isMut": true,
|
||||
|
@ -7824,7 +7843,7 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "flashLoanVaultInitial",
|
||||
"name": "flashLoanTokenAccountInitial",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
|
@ -9444,6 +9463,11 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "PerpBalanceLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -9494,6 +9518,11 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "TokenBalanceLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -9527,39 +9556,13 @@ export const IDL: MangoV4 = {
|
|||
]
|
||||
},
|
||||
{
|
||||
"name": "MarginTradeLog",
|
||||
"name": "FlashLoanLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "tokenIndexes",
|
||||
"type": {
|
||||
"vec": "u16"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "preIndexedPositions",
|
||||
"type": {
|
||||
"vec": "i128"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "postIndexedPositions",
|
||||
"type": {
|
||||
"vec": "i128"
|
||||
},
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FlashLoanLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -9579,6 +9582,11 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "WithdrawLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -9609,6 +9617,11 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "DepositLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
|
@ -9834,6 +9847,11 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "LiquidateTokenAndTokenLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "liqee",
|
||||
"type": "publicKey",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { AnchorProvider, Wallet } from '@project-serum/anchor';
|
||||
import { Connection, Keypair } from '@solana/web3.js';
|
||||
import fs from 'fs';
|
||||
import { AccountSize } from '../accounts/mangoAccount';
|
||||
import { MangoClient } from '../client';
|
||||
import { MANGO_V4_ID } from '../constants';
|
||||
|
||||
|
@ -44,10 +43,6 @@ async function main() {
|
|||
const mangoAccount = await client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user.publicKey,
|
||||
user,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||
console.log(mangoAccount.toString());
|
||||
|
@ -55,11 +50,11 @@ async function main() {
|
|||
if (false) {
|
||||
// deposit and withdraw
|
||||
console.log(`Depositing...50 USDC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50, user);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`Depositing...0.0005 BTC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005, user);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
|
||||
await mangoAccount.reload(client, group);
|
||||
}
|
||||
try {
|
||||
|
|
|
@ -92,11 +92,11 @@ async function main() {
|
|||
if (true) {
|
||||
// deposit
|
||||
console.log(`...depositing 50 USDC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50, user);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`...depositing 0.0005 BTC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005, user);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
// serum3
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AnchorProvider, Wallet } from '@project-serum/anchor';
|
||||
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
import fs from 'fs';
|
||||
import { AccountSize, HealthType } from '../accounts/mangoAccount';
|
||||
import { HealthType } from '../accounts/mangoAccount';
|
||||
import { OrderType, Side } from '../accounts/perp';
|
||||
import {
|
||||
Serum3OrderType,
|
||||
|
@ -50,7 +50,10 @@ async function main() {
|
|||
JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')),
|
||||
),
|
||||
);
|
||||
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
|
||||
// const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
|
||||
const group = await client.getGroup(
|
||||
new PublicKey('FdynL6q7CNJMMiTZpfnYVkqQRYaoiBWgWkFYvvpx9uA8'),
|
||||
);
|
||||
console.log(group.toString());
|
||||
|
||||
// create + fetch account
|
||||
|
@ -58,10 +61,6 @@ async function main() {
|
|||
const mangoAccount = await client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user.publicKey,
|
||||
user,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||
console.log(mangoAccount.toString());
|
||||
|
@ -98,11 +97,15 @@ async function main() {
|
|||
|
||||
try {
|
||||
console.log(`...depositing 50 USDC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50, user);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`...withdrawing 1 USDC`);
|
||||
await client.tokenWithdraw(group, mangoAccount, 'USDC', 1, true);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`...depositing 0.0005 BTC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005, user);
|
||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
|
||||
await mangoAccount.reload(client, group);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AnchorProvider, Wallet } from '@project-serum/anchor';
|
||||
import { Connection, Keypair } from '@solana/web3.js';
|
||||
import fs from 'fs';
|
||||
import { AccountSize, HealthType } from '../accounts/mangoAccount';
|
||||
import { HealthType } from '../accounts/mangoAccount';
|
||||
import { MangoClient } from '../client';
|
||||
import { MANGO_V4_ID } from '../constants';
|
||||
import { toUiDecimals } from '../utils';
|
||||
|
@ -43,9 +43,6 @@ async function main() {
|
|||
const mangoAccount = await client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user.publicKey,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||
console.log(mangoAccount.toString());
|
||||
|
|
|
@ -101,7 +101,6 @@ async function main() {
|
|||
group.findBank(token.tokenIndex)!.name,
|
||||
token.native(group.findBank(token.tokenIndex)!).toNumber(),
|
||||
false,
|
||||
user,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -45,21 +45,17 @@ async function main() {
|
|||
const mangoAccount = await client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user.publicKey,
|
||||
user,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||
console.log(mangoAccount.toString(group));
|
||||
|
||||
if (false) {
|
||||
console.log(`...depositing 10 USDC`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 10, user);
|
||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 10);
|
||||
await mangoAccount.reload(client, group);
|
||||
|
||||
console.log(`...depositing 1 SOL`);
|
||||
await client.tokenDeposit(group, mangoAccount, 'SOL', 1, user);
|
||||
await client.tokenDeposit(group, mangoAccount, 'SOL', 1);
|
||||
await mangoAccount.reload(client, group);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,10 +60,6 @@ async function main() {
|
|||
const mangoAccount = await client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user.publicKey,
|
||||
user,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||
console.log(`start balance \n${mangoAccount.toString(group)}`);
|
||||
|
|
|
@ -40,10 +40,6 @@ async function main() {
|
|||
const user1MangoAccount = await user1Client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user1.publicKey,
|
||||
user1,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
|
||||
console.log(`...mangoAccount1 ${user1MangoAccount.publicKey}`);
|
||||
|
@ -52,7 +48,7 @@ async function main() {
|
|||
let amount = 0.001;
|
||||
let token = 'BTC';
|
||||
console.log(`Depositing...${amount} 'BTC'`);
|
||||
await user1Client.tokenDeposit(group, user1MangoAccount, token, amount, user1);
|
||||
await user1Client.tokenDeposit(group, user1MangoAccount, token, amount);
|
||||
await user1MangoAccount.reload(user1Client, group);
|
||||
console.log(`${user1MangoAccount.toString(group)}`);
|
||||
|
||||
|
@ -74,10 +70,6 @@ async function main() {
|
|||
const user2MangoAccount = await user2Client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user2.publicKey,
|
||||
user2,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
console.log(`...mangoAccount2 ${user2MangoAccount.publicKey}`);
|
||||
|
||||
|
@ -98,7 +90,7 @@ async function main() {
|
|||
/// user2 deposits some collateral and borrows BTC
|
||||
amount = 1;
|
||||
console.log(`Depositing...${amount} 'USDC'`);
|
||||
await user2Client.tokenDeposit(group, user2MangoAccount, 'USDC', amount, user2);
|
||||
await user2Client.tokenDeposit(group, user2MangoAccount, 'USDC', amount);
|
||||
await user2MangoAccount.reload(user2Client, group);
|
||||
console.log(`${user2MangoAccount.toString(group)}`);
|
||||
|
||||
|
@ -113,7 +105,6 @@ async function main() {
|
|||
token,
|
||||
amount,
|
||||
true,
|
||||
user2
|
||||
);
|
||||
await user2MangoAccount.reload(user2Client, group);
|
||||
console.log(`${user2MangoAccount.toString(group)}`);
|
||||
|
|
|
@ -45,10 +45,6 @@ async function main() {
|
|||
const mangoAccount = await client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user.publicKey,
|
||||
user,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||
|
||||
|
|
|
@ -46,10 +46,6 @@ async function main() {
|
|||
const mangoAccount = await client.getOrCreateMangoAccount(
|
||||
group,
|
||||
user.publicKey,
|
||||
user,
|
||||
0,
|
||||
AccountSize.small,
|
||||
'my_mango_account',
|
||||
);
|
||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||
|
||||
|
|
|
@ -23,9 +23,9 @@ export function debugAccountMetas(ams: AccountMeta[]) {
|
|||
|
||||
export async function findOrCreate<T>(
|
||||
entityName: string,
|
||||
findMethod: Function,
|
||||
findMethod: (...x: any) => any,
|
||||
findArgs: any[],
|
||||
createMethod: Function,
|
||||
createMethod: (...x: any) => any,
|
||||
createArgs: any[],
|
||||
): Promise<T> {
|
||||
let many: T[] = await findMethod(...findArgs);
|
||||
|
|
Loading…
Reference in New Issue