use crate::chain_client::named_chain_for;
use alloy::network::EthereumWallet;
use alloy::primitives::{Address, U160, U256};
use alloy::providers::{Provider, ProviderBuilder};
use alloy::signers::local::PrivateKeySigner;
use eyre::Result;
use url::Url;
use crate::chain_client::ARCH_SOLANA;
use crate::commands::config::config_pb::GetConfigResponse;
use crate::evm::rpc::{IERC20, MidribV3};
use crate::wallet::{CurveType, Wallet};
const MIN_GAS_BALANCE: u128 = 100_000_000_000_000;
pub async fn call_deposit_from_config_with_wallet(
network: String,
token_symbol: String,
amount: u128,
wallet: &Wallet,
config: GetConfigResponse,
) -> Result<()> {
let chain_for_arch = config
.get_chain(&network)
.ok_or_else(|| eyre::eyre!("Chain '{}' not found in configuration", network))?;
if chain_for_arch
.architecture
.eq_ignore_ascii_case(ARCH_SOLANA)
{
let spl_amount: u64 = amount.try_into().map_err(|_| {
eyre::eyre!("amount {amount} exceeds the SPL token u64 max on Solana chain '{network}'")
})?;
return solana_deposit(chain_for_arch, &token_symbol, spl_amount, wallet).await;
}
if wallet.curve() != CurveType::Secp256k1 {
return Err(eyre::eyre!(
"EVM chain '{}' requires a secp256k1 wallet, got {:?}",
network,
wallet.curve()
));
}
let signer = wallet
.as_evm()
.ok_or_else(|| eyre::eyre!("expected EVM wallet for chain '{}'", network))?
.clone();
call_deposit_from_config_evm(network, token_symbol, amount, signer, config).await
}
#[cfg(feature = "solana")]
async fn solana_deposit(
chain: &crate::commands::config::config_pb::Chain,
token_symbol: &str,
amount: u64,
wallet: &Wallet,
) -> Result<()> {
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
let token = chain.tokens.get(token_symbol).ok_or_else(|| {
eyre::eyre!(
"Token '{}' not found on Solana chain '{}'",
token_symbol,
chain.network
)
})?;
let keypair = wallet.as_solana().ok_or_else(|| {
eyre::eyre!(
"Solana chain '{}' requires an Ed25519 wallet (TRADER_PRIVKEY_SOLANA)",
chain.network
)
})?;
let (program_id, instance) = crate::solana::client::resolve_program_and_instance(chain)?;
let rpc_url = crate::chain_client::checked_chain_rpc_url(chain).await?;
let user = solana_sdk::signer::Signer::pubkey(keypair);
let mint = Pubkey::from_str(&token.address)
.map_err(|e| eyre::eyre!("invalid Solana mint '{}': {}", token.address, e))?;
let user_ata = crate::solana::derive_associated_token_account(&user, &mint);
tracing::info!(
"Solana deposit: {} {} on {} (program={}, instance={}, ata={})",
amount,
token_symbol,
chain.network,
program_id,
instance,
user_ata
);
let ix = crate::solana::deposit_ix(&program_id, &instance, &user, &mint, &user_ata, amount)?;
if crate::solana::is_wsol_mint(&token.address) {
use solana_client::nonblocking::rpc_client::RpcClient;
const WRAP_FEE_HEADROOM: u64 = 3_000_000;
let rpc = RpcClient::new(rpc_url.clone());
let lamports = rpc.get_balance(&user).await.unwrap_or(0);
let required = amount.saturating_add(WRAP_FEE_HEADROOM);
if lamports < required {
return Err(eyre::eyre!(
"insufficient SOL: wallet {user} has {lamports} lamports, needs \
{required} (deposit amount + ~0.003 SOL fee/rent headroom)"
));
}
let ata_ix = crate::solana::create_idempotent_ata_ix(&user, &user, &mint, &user_ata);
let wrap_ix = crate::solana::system_transfer_ix(&user, &user_ata, amount);
let sync_ix = crate::solana::sync_native_ix(&user_ata);
let ixs = [ata_ix, wrap_ix, sync_ix, ix];
let sig = crate::solana::client::submit_user_signed_multi(&rpc_url, keypair, &ixs).await?;
tracing::info!("Solana native (wrapped-SOL) deposit confirmed: {}", sig);
return Ok(());
}
let sig = crate::solana::client::submit_user_signed(&rpc_url, keypair, ix).await?;
tracing::info!("Solana deposit confirmed: {}", sig);
Ok(())
}
#[cfg(not(feature = "solana"))]
async fn solana_deposit(
chain: &crate::commands::config::config_pb::Chain,
_token_symbol: &str,
_amount: u64,
_wallet: &Wallet,
) -> Result<()> {
Err(eyre::eyre!(
"chain '{}' is Solana but the `solana` feature is disabled",
chain.network
))
}
async fn call_deposit_from_config_evm(
network: String,
token_symbol: String,
amount: u128,
signer: PrivateKeySigner,
config: GetConfigResponse,
) -> Result<()> {
let chain = config.get_chain(&network).ok_or_else(|| {
let available_chains = config
.config
.as_ref()
.map(|c| {
c.chains
.iter()
.map(|ch| ch.network.as_str())
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_default();
eyre::eyre!(
"Chain '{}' not found in configuration. Available chains: {}",
network,
available_chains
)
})?;
let token = config.get_token(&network, &token_symbol).ok_or_else(|| {
let available_tokens = chain
.tokens
.keys()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ");
eyre::eyre!(
"Token '{}' not found on chain '{}'. Available tokens: {}",
token_symbol,
network,
available_tokens
)
})?;
let contract_address = chain
.trade_contract
.as_ref()
.ok_or_else(|| {
eyre::eyre!(
"Trade contract not found for chain '{}'. Please ensure the contract is deployed.",
network
)
})?
.address
.clone();
let named_chain = named_chain_for(chain.chain_id as u64);
let rpc_url = crate::chain_client::checked_chain_rpc_url(chain).await?;
tracing::info!(
"Depositing {} {} on {} (chain_id: {}, rpc: {})",
amount,
token_symbol,
network,
chain.chain_id,
crate::chain_client::redact_rpc_url(&rpc_url)
);
let native = crate::evm::is_native_token(&token.address);
let allowance_amount = U256::from(amount.saturating_add(1000));
let deposit_amount = U160::from(amount);
let contract_addr: Address = contract_address.parse()?;
let token_addr: Address = token.address.parse()?;
let signer_address = signer.address();
let wallet = EthereumWallet::new(signer);
let rpc_url = Url::parse(&rpc_url)?;
let provider = match named_chain {
Some(named) => ProviderBuilder::new()
.with_chain(named)
.wallet(wallet)
.connect_http(rpc_url)
.erased(),
None => ProviderBuilder::new()
.wallet(wallet)
.connect_http(rpc_url)
.erased(),
};
let gas_balance = provider.get_balance(signer_address).await?;
tracing::info!("Gas balance: {} wei", gas_balance);
let required = if native {
U256::from(amount).saturating_add(U256::from(MIN_GAS_BALANCE))
} else {
U256::from(MIN_GAS_BALANCE)
};
if gas_balance < required {
let balance_eth = gas_balance.to::<u128>() as f64 / 1e18;
return Err(eyre::eyre!(
"insufficient native balance: wallet has {:.6}, needs {} wei \
({}). Fund your wallet ({}) on {}.",
balance_eth,
required,
if native {
"deposit amount + 0.0001 gas headroom"
} else {
"0.0001 gas headroom"
},
signer_address,
network
));
}
let contract = MidribV3::new(contract_addr, &provider);
if native {
tracing::info!("Attempting NATIVE deposit of {amount} wei to contract {contract_addr}");
let deposit_tx = contract.depositNative().value(U256::from(amount));
match deposit_tx.estimate_gas().await {
Ok(gas_estimate) => {
tracing::info!("Gas estimate for depositNative: {gas_estimate:?}");
}
Err(e) => {
tracing::error!("Failed to estimate gas for depositNative: {e:?}");
return Err(e.into());
}
}
let result = deposit_tx.send().await?;
tracing::info!("Native deposit transaction sent: {result:?}");
let receipt = result.with_required_confirmations(1).watch().await?;
tracing::info!("Native deposit transaction hash: {receipt:?}");
return Ok(());
}
let erc20 = IERC20::new(token_addr, &provider);
let allowance_result = erc20
.allowance(signer_address, contract_addr)
.call()
.await?;
tracing::info!("Get allowance result: {allowance_result:?}");
if allowance_result < allowance_amount {
tracing::info!(
"Current allowance insufficient, approving {} tokens",
allowance_amount
);
let approve_result = erc20
.approve(contract_addr, allowance_amount)
.send()
.await?
.watch()
.await?;
tracing::info!("Set allowance result: {approve_result:?}");
} else {
tracing::info!("Sufficient allowance already set: {}", allowance_result);
}
tracing::info!("Attempting deposit of {deposit_amount} tokens to contract {contract_addr}");
let deposit_tx = contract.deposit(token_addr, deposit_amount);
match deposit_tx.estimate_gas().await {
Ok(gas_estimate) => {
tracing::info!("Gas estimate for deposit: {gas_estimate:?}");
}
Err(e) => {
tracing::error!("Failed to estimate gas for deposit: {e:?}");
return Err(e.into());
}
}
let result = deposit_tx.send().await?;
tracing::info!("Deposit transaction sent: {result:?}");
let receipt = result.with_required_confirmations(1).watch().await?;
tracing::info!("Deposit transaction hash: {receipt:?}");
Ok(())
}