client: construct from account pubkeys

This commit is contained in:
Christian Kamm 2022-08-01 14:19:52 +02:00
parent 16290e07f4
commit b0dae7ec22
5 changed files with 137 additions and 150 deletions

View File

@ -1,7 +1,9 @@
use std::rc::Rc;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use anchor_client::{Client, ClientError, Cluster, Program};
use anchor_client::{ClientError, Cluster, Program};
use anchor_lang::__private::bytemuck;
use anchor_lang::prelude::System;
@ -28,17 +30,45 @@ use solana_sdk::signature::{Keypair, Signature};
use solana_sdk::sysvar;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signer::Signer};
// very close to anchor_client::Client, which unfortunately has no accessors or Clone
#[derive(Clone, Debug)]
pub struct Client {
pub cluster: Cluster,
pub fee_payer: Arc<Keypair>,
pub commitment: CommitmentConfig,
}
impl Client {
pub fn new(cluster: Cluster, commitment: CommitmentConfig, fee_payer: &Keypair) -> Self {
Self {
cluster,
fee_payer: Arc::new(fee_payer.clone()),
commitment,
}
}
pub fn anchor_client(&self) -> anchor_client::Client {
anchor_client::Client::new_with_options(
self.cluster.clone(),
Rc::new((*self.fee_payer).clone()),
self.commitment,
)
}
pub fn rpc_with_timeout(&self, timeout: Duration) -> RpcClient {
RpcClient::new_with_timeout_and_commitment(self.cluster.clone(), timeout, self.commitment)
}
}
// todo: might want to integrate geyser, websockets, or simple http polling for keeping data fresh
pub struct MangoClient {
pub rpc: RpcClient,
pub cluster: Cluster,
pub commitment: CommitmentConfig,
pub client: Client,
// todo: possibly this object should have cache-functions, so there can be one getMultipleAccounts
// call to refresh banks etc -- if it's backed by websockets, these could just do nothing
pub account_fetcher: Arc<dyn AccountFetcher>,
pub payer: Keypair,
pub owner: Keypair,
pub mango_account_address: Pubkey,
pub context: MangoGroupContext,
@ -58,48 +88,17 @@ impl MangoClient {
.0
}
/// Conveniently creates a RPC based client
pub fn new(
cluster: Cluster,
commitment: CommitmentConfig,
pub fn find_or_create_account(
client: &Client,
group: Pubkey,
payer: Keypair,
owner: Keypair,
payer: Keypair, // pays the SOL for the new account
mango_account_name: &str,
) -> anyhow::Result<Self> {
let group_context = MangoGroupContext::new_from_rpc(group, cluster.clone(), commitment)?;
let rpc = RpcClient::new_with_commitment(cluster.url().to_string(), commitment);
let account_fetcher = Arc::new(CachedAccountFetcher::new(RpcAccountFetcher { rpc }));
Self::new_detail(
cluster,
commitment,
payer,
mango_account_name,
group_context,
account_fetcher,
)
}
/// Allows control of AccountFetcher and externally created MangoGroupContext
pub fn new_detail(
cluster: Cluster,
commitment: CommitmentConfig,
payer: Keypair,
mango_account_name: &str,
// future: maybe pass Arc<MangoGroupContext>, so it can be extenally updated?
group_context: MangoGroupContext,
account_fetcher: Arc<dyn AccountFetcher>,
) -> anyhow::Result<Self> {
let program =
Client::new_with_options(cluster.clone(), std::rc::Rc::new(payer.clone()), commitment)
.program(mango_v4::ID);
let rpc = program.rpc();
let group = group_context.group;
) -> anyhow::Result<Pubkey> {
let program = client.anchor_client().program(mango_v4::ID);
// Mango Account
let mut mango_account_tuples = fetch_mango_accounts(&program, group, payer.pubkey())?;
let mut mango_account_tuples = fetch_mango_accounts(&program, group, owner.pubkey())?;
let mango_account_opt = mango_account_tuples
.iter()
.find(|(_, account)| account.fixed.name() == mango_account_name);
@ -121,13 +120,13 @@ impl MangoClient {
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::AccountCreate {
group,
owner: payer.pubkey(),
owner: owner.pubkey(),
account: {
Pubkey::find_program_address(
&[
group.as_ref(),
b"MangoAccount".as_ref(),
payer.pubkey().as_ref(),
owner.pubkey().as_ref(),
&account_num.to_le_bytes(),
],
&mango_v4::id(),
@ -147,42 +146,72 @@ impl MangoClient {
},
),
})
.signer(&owner)
.signer(&payer)
.send()
.map_err(prettify_client_error)
.context("Failed to create account...")?;
}
let mango_account_tuples = fetch_mango_accounts(&program, group, payer.pubkey())?;
let mango_account_tuples = fetch_mango_accounts(&program, group, owner.pubkey())?;
let index = mango_account_tuples
.iter()
.position(|tuple| tuple.1.fixed.name() == mango_account_name)
.unwrap();
let mango_account_cache = &mango_account_tuples[index];
Ok(mango_account_tuples[index].0)
}
/// 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 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;
if mango_account.fixed.owner != owner.pubkey() {
anyhow::bail!(
"bad owner for account: expected {} got {}",
mango_account.fixed.owner,
owner.pubkey()
);
}
let group_context =
MangoGroupContext::new_from_rpc(group, client.cluster.clone(), client.commitment)?;
Self::new_detail(client, account, owner, group_context, account_fetcher)
}
/// Allows control of AccountFetcher and externally created MangoGroupContext
pub fn new_detail(
client: Client,
account: Pubkey,
owner: Keypair,
// future: maybe pass Arc<MangoGroupContext>, so it can be extenally updated?
group_context: MangoGroupContext,
account_fetcher: Arc<dyn AccountFetcher>,
) -> anyhow::Result<Self> {
Ok(Self {
rpc,
cluster,
commitment,
client,
account_fetcher,
payer,
mango_account_address: mango_account_cache.0,
owner,
mango_account_address: account,
context: group_context,
})
}
pub fn client(&self) -> Client {
Client::new_with_options(
self.cluster.clone(),
std::rc::Rc::new(self.payer.clone()),
self.commitment,
)
pub fn anchor_client(&self) -> anchor_client::Client {
self.client.anchor_client()
}
pub fn program(&self) -> Program {
self.client().program(mango_v4::ID)
self.anchor_client().program(mango_v4::ID)
}
pub fn payer(&self) -> Pubkey {
self.payer.pubkey()
pub fn owner(&self) -> Pubkey {
self.owner.pubkey()
}
pub fn group(&self) -> Pubkey {
@ -282,10 +311,10 @@ impl MangoClient {
bank: mint_info.first_bank(),
vault: mint_info.first_vault(),
token_account: get_associated_token_address(
&self.payer(),
&self.owner(),
&mint_info.mint,
),
token_authority: self.payer(),
token_authority: self.owner(),
token_program: Token::id(),
},
None,
@ -297,6 +326,7 @@ impl MangoClient {
amount,
}),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)
}
@ -348,8 +378,8 @@ impl MangoClient {
serum_program: serum3_info.market.serum_program,
serum_market_external: serum3_info.market.serum_market_external,
open_orders,
owner: self.payer(),
payer: self.payer(),
owner: self.owner(),
payer: self.owner(),
system_program: System::id(),
rent: sysvar::rent::id(),
},
@ -359,6 +389,7 @@ impl MangoClient {
&mango_v4::instruction::Serum3CreateOpenOrders {},
),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)
}
@ -482,7 +513,7 @@ impl MangoClient {
market_base_vault: s3.market.coin_vault,
market_quote_vault: s3.market.pc_vault,
market_vault_signer: s3.market.vault_signer,
owner: self.payer(),
owner: self.owner(),
token_program: Token::id(),
},
None,
@ -503,6 +534,7 @@ impl MangoClient {
},
),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)
}
@ -532,7 +564,7 @@ impl MangoClient {
market_base_vault: s3.market.coin_vault,
market_quote_vault: s3.market.pc_vault,
market_vault_signer: s3.market.vault_signer,
owner: self.payer(),
owner: self.owner(),
token_program: Token::id(),
},
None,
@ -541,6 +573,7 @@ impl MangoClient {
&mango_v4::instruction::Serum3SettleFunds {},
),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)
}
@ -602,7 +635,7 @@ impl MangoClient {
market_bids: s3.market.bids,
market_asks: s3.market.asks,
market_event_queue: s3.market.event_q,
owner: self.payer(),
owner: self.owner(),
},
None,
)
@ -611,6 +644,7 @@ impl MangoClient {
&mango_v4::instruction::Serum3CancelOrder { side, order_id },
),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)?;
@ -650,7 +684,7 @@ impl MangoClient {
group: self.group(),
liqee: *liqee.0,
liqor: self.mango_account_address,
liqor_owner: self.payer.pubkey(),
liqor_owner: self.owner(),
},
None,
);
@ -665,6 +699,7 @@ impl MangoClient {
},
),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)
}
@ -714,7 +749,7 @@ impl MangoClient {
group: self.group(),
liqee: *liqee.0,
liqor: self.mango_account_address,
liqor_owner: self.payer.pubkey(),
liqor_owner: self.owner(),
liab_mint_info: liab_info.mint_info_address,
quote_vault: quote_info.mint_info.first_vault(),
insurance_vault: group.insurance_vault,
@ -733,6 +768,7 @@ impl MangoClient {
},
),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)
}

View File

@ -1,4 +1,3 @@
RPC_URL=
PAYER_KEYPAIR=
GROUP=
MANGO_ACCOUNT_NAME=
MANGO_ACCOUNT=
OWNER=

View File

@ -6,9 +6,9 @@ use std::sync::Arc;
use anchor_client::Cluster;
use clap::{Parser, Subcommand};
use client::{keypair_from_cli, MangoClient};
use client::{keypair_from_cli, Client, MangoClient};
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::{commitment_config::CommitmentConfig, signature::Signer};
use tokio::time;
// TODO
@ -34,21 +34,11 @@ struct Cli {
#[clap(short, long, env)]
rpc_url: String,
#[clap(short, long, env = "PAYER_KEYPAIR")]
payer: String,
#[clap(short, long, env)]
mango_account: Pubkey,
#[clap(short, long, env)]
group: Option<Pubkey>,
// These exist only as a shorthand to make testing easier. Normal users would provide the group.
#[clap(long, env)]
group_from_admin_keypair: Option<String>,
#[clap(long, env, default_value = "0")]
group_from_admin_num: u32,
#[clap(short, long, env)]
mango_account_name: String,
owner: String,
#[clap(subcommand)]
command: Command,
@ -73,7 +63,7 @@ fn main() -> Result<(), anyhow::Error> {
};
let cli = Cli::parse_from(args);
let payer = keypair_from_cli(&cli.payer);
let owner = keypair_from_cli(&cli.owner);
let rpc_url = cli.rpc_url;
let ws_url = rpc_url.replace("https", "wss");
@ -84,21 +74,10 @@ fn main() -> Result<(), anyhow::Error> {
Command::Taker { .. } => CommitmentConfig::confirmed(),
};
let group = if let Some(group) = cli.group {
group
} else if let Some(p) = cli.group_from_admin_keypair {
let admin = keypair_from_cli(&p);
MangoClient::group_for_admin(admin.pubkey(), cli.group_from_admin_num)
} else {
panic!("Must provide either group or group_from_admin_keypair");
};
let mango_client = Arc::new(MangoClient::new(
cluster,
commitment,
group,
payer,
&cli.mango_account_name,
let mango_client = Arc::new(MangoClient::new_for_existing_account(
Client::new(cluster, commitment, &owner),
cli.mango_account,
owner,
)?);
let rt = tokio::runtime::Builder::new_multi_thread()

View File

@ -1,6 +1,4 @@
RPC_URL=https://mango.devnet.rpcpool.com
GROUP=9njq91AuvDD6MXxr1uPiHY1B5ACHv6r6z5ZrCpz7sqtQ
PYTH_PROGRAM=gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s
SERUM_PROGRAM=DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY
LIQOR_MANGO_ACCOUNT=
LIQOR_OWNER=~/.config/solana/user.json
LIQOR_MANGO_ACCOUNT_NAME=my_mango_account

View File

@ -4,14 +4,12 @@ use std::time::Duration;
use anchor_client::Cluster;
use clap::Parser;
use client::{chain_data, keypair_from_cli, MangoClient, MangoGroupContext};
use client::{chain_data, keypair_from_cli, Client, MangoClient, MangoGroupContext};
use log::*;
use mango_v4::state::{PerpMarketIndex, TokenIndex};
use solana_client::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signer::Signer;
use std::collections::HashSet;
pub mod account_shared_data;
@ -56,29 +54,15 @@ struct Cli {
#[clap(short, long, env)]
rpc_url: String,
#[clap(long, env)]
group: Option<Pubkey>,
// These exist only as a shorthand to make testing easier. Normal users would provide the group.
#[clap(long, env)]
group_from_admin_keypair: Option<String>,
#[clap(long, env, default_value = "0")]
group_from_admin_num: u32,
// TODO: maybe store this in the group, so it's easy to start without providing it?
#[clap(long, env)]
pyth_program: Pubkey,
// TODO: different serum markets could use different serum programs, should come from registered markets
#[clap(long, env)]
serum_program: Pubkey,
#[clap(long, env)]
liqor_owner: String,
liqor_mango_account: Pubkey,
#[clap(long, env)]
liqor_mango_account_name: String,
liqor_owner: String,
#[clap(long, env, default_value = "300")]
snapshot_interval_secs: u64,
@ -109,21 +93,25 @@ async fn main() -> anyhow::Result<()> {
let liqor_owner = keypair_from_cli(&cli.liqor_owner);
let mango_group = if let Some(group) = cli.group {
group
} else if let Some(p) = cli.group_from_admin_keypair {
let admin = keypair_from_cli(&p);
MangoClient::group_for_admin(admin.pubkey(), cli.group_from_admin_num)
} else {
panic!("Must provide either group or group_from_admin_keypair");
};
let rpc_url = cli.rpc_url;
let ws_url = rpc_url.replace("https", "wss");
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);
// 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),
});
let mango_account = account_fetcher.fetch_fresh_mango_account(&cli.liqor_mango_account)?;
let mango_group = mango_account.fixed.group;
let group_context = MangoGroupContext::new_from_rpc(mango_group, cluster.clone(), commitment)?;
// TODO: this is all oracles, not just pyth!
@ -185,18 +173,6 @@ async fn main() -> anyhow::Result<()> {
snapshot_sender,
);
// 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: RpcClient::new_with_timeout_and_commitment(
cluster.url().to_string(),
rpc_timeout,
commitment,
),
});
start_chain_data_metrics(chain_data.clone(), &metrics);
// Addresses of the MangoAccounts belonging to the mango program.
@ -227,10 +203,9 @@ async fn main() -> anyhow::Result<()> {
//
let mango_client = {
Arc::new(MangoClient::new_detail(
cluster,
commitment,
client,
cli.liqor_mango_account,
liqor_owner,
&cli.liqor_mango_account_name,
group_context,
account_fetcher.clone(),
)?)