use eyre::{eyre, Result};
use solana_sdk::{
instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer,
transaction::Transaction,
};
use std::str::FromStr;
use crate::commands::config::config_pb::Chain;
use crate::solana::derive_user_balance_pda;
pub fn resolve_program_and_instance(chain: &Chain) -> Result<(Pubkey, Pubkey)> {
let program_str = if !chain.factory_address.is_empty() {
chain.factory_address.clone()
} else {
chain
.trade_contract
.as_ref()
.and_then(|tc| tc.contract_id.clone())
.ok_or_else(|| {
eyre!(
"Solana chain '{}' has no factory_address / trade_contract.contract_id (program id) configured",
chain.network
)
})?
};
let program_id = Pubkey::from_str(&program_str)
.map_err(|e| eyre!("invalid Solana program id '{}': {}", program_str, e))?;
let instance_str = chain
.trade_contract
.as_ref()
.map(|tc| tc.address.clone())
.filter(|s| !s.is_empty())
.ok_or_else(|| {
eyre!(
"Solana chain '{}' has no trade_contract.address (instance PDA) configured",
chain.network
)
})?;
let instance = Pubkey::from_str(&instance_str)
.map_err(|e| eyre!("invalid Solana instance address '{}': {}", instance_str, e))?;
Ok((program_id, instance))
}
pub async fn submit_user_signed(
rpc_url: &str,
user_keypair: &Keypair,
ix: Instruction,
) -> Result<String> {
use solana_client::nonblocking::rpc_client::RpcClient;
let client = RpcClient::new(rpc_url.to_string());
let blockhash = client
.get_latest_blockhash()
.await
.map_err(|e| eyre!("get_latest_blockhash: {}", e))?;
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&user_keypair.pubkey()),
&[user_keypair],
blockhash,
);
let sig = client
.send_and_confirm_transaction(&tx)
.await
.map_err(|e| eyre!("send_and_confirm_transaction: {}", e))?;
Ok(sig.to_string())
}
pub async fn fetch_user_balance(
rpc_url: &str,
instance: &Pubkey,
user: &Pubkey,
mint: &Pubkey,
program_id: &Pubkey,
) -> Result<(u64, u64)> {
use solana_client::nonblocking::rpc_client::RpcClient;
let client = RpcClient::new(rpc_url.to_string());
let (pda, _) = derive_user_balance_pda(instance, user, mint, program_id);
let response = client
.get_account_with_commitment(&pda, client.commitment())
.await
.map_err(|e| eyre!("get_account (UserBalance PDA): {}", e))?;
let Some(acc) = response.value else {
return Ok((0, 0));
};
const DEPOSITED_OFFSET: usize = 8 + 32 + 32 + 32;
const LOCKED_OFFSET: usize = DEPOSITED_OFFSET + 8;
if acc.data.len() < LOCKED_OFFSET + 8 {
return Err(eyre!(
"UserBalance account too small: {} bytes",
acc.data.len()
));
}
let deposited_bytes: [u8; 8] = acc.data[DEPOSITED_OFFSET..DEPOSITED_OFFSET + 8]
.try_into()
.map_err(|_| eyre!("UserBalance account data layout error (deposited)"))?;
let locked_bytes: [u8; 8] = acc.data[LOCKED_OFFSET..LOCKED_OFFSET + 8]
.try_into()
.map_err(|_| eyre!("UserBalance account data layout error (locked)"))?;
Ok((
u64::from_le_bytes(deposited_bytes),
u64::from_le_bytes(locked_bytes),
))
}