#![allow(dead_code)]
use crate::{commands::wallet::CliState, errors::CliError};
use neo3::{
neo_clients::{APITrait, HttpProvider, RpcClient},
neo_types::AddressExtension,
neo_wallets::{Nep6Wallet, Wallet},
prelude::*,
sdk::DecimalAmount,
};
use num_traits::cast::ToPrimitive;
use std::{path::PathBuf, str::FromStr};
pub enum NetworkType {
MainNet,
TestNet,
PrivateNet,
}
impl NetworkType {
pub fn from_network(network: &str) -> Self {
match network.to_lowercase().as_str() {
"mainnet" => NetworkType::MainNet,
"testnet" => NetworkType::TestNet,
_ => NetworkType::PrivateNet,
}
}
}
#[derive(Clone, Copy)]
pub enum NetworkTypeCli {
MainNet, TestNet, NeoX,
}
impl NetworkTypeCli {
pub fn from_magic(magic: u32) -> Self {
match magic {
769 => NetworkTypeCli::MainNet,
894 => NetworkTypeCli::TestNet,
_ => NetworkTypeCli::NeoX,
}
}
pub fn from_network_string(network: &str) -> Self {
match network.to_lowercase().as_str() {
"mainnet" => NetworkTypeCli::MainNet,
"testnet" => NetworkTypeCli::TestNet,
_ => NetworkTypeCli::TestNet, }
}
pub fn to_network_string(&self) -> String {
match self {
NetworkTypeCli::MainNet => "MainNet".to_string(),
NetworkTypeCli::TestNet => "TestNet".to_string(),
NetworkTypeCli::NeoX => "NeoX".to_string(),
}
}
}
pub async fn load_wallet(
wallet_path: &PathBuf,
password: Option<&str>,
) -> Result<Wallet, CliError> {
if !wallet_path.exists() {
return Err(CliError::Wallet(format!("Wallet file not found: {}", wallet_path.display())));
}
let wallet = match password {
Some(pwd) => Wallet::open_wallet(wallet_path, pwd)
.map_err(|e| CliError::Wallet(format!("Failed to open wallet: {}", e)))?,
None => {
let wallet_json = std::fs::read_to_string(wallet_path)
.map_err(|e| CliError::Wallet(format!("Failed to read wallet file: {}", e)))?;
let nep6: Nep6Wallet = serde_json::from_str(&wallet_json)
.map_err(|e| CliError::Wallet(format!("Failed to parse wallet file: {}", e)))?;
Wallet::from_nep6(nep6)
.map_err(|e| CliError::Wallet(format!("Failed to load wallet: {}", e)))?
},
};
Ok(wallet)
}
pub fn prepare_state_from_existing(existing_state: &CliState) -> CliState {
let mut new_state = CliState::default();
if let Some(wallet) = &existing_state.wallet {
new_state.wallet = Some(wallet.clone());
}
new_state.wallet_path = existing_state.wallet_path.clone();
new_state.wallet_password = existing_state.wallet_password.clone();
if let Some(rpc_client) = &existing_state.rpc_client {
new_state.rpc_client = Some(rpc_client.clone());
}
new_state.network_type = existing_state.network_type.clone();
new_state
}
pub fn get_token_address_for_network(
token_symbol: &str,
network_type: NetworkTypeCli,
) -> Option<ScriptHash> {
let token_symbol = token_symbol.to_uppercase();
match network_type {
NetworkTypeCli::MainNet => {
match token_symbol.as_str() {
"NEO" => ScriptHash::from_str("ef4073a0f2b305a38ec4050e4d3d28bc40ea63f5").ok(),
"GAS" => ScriptHash::from_str("d2a4cff31913016155e38e474a2c06d08be276cf").ok(),
"FLM" => ScriptHash::from_str("f0151f528127558851b39c2cd8aa47da7418ab28").ok(),
_ => None,
}
},
NetworkTypeCli::TestNet => {
match token_symbol.as_str() {
"NEO" => ScriptHash::from_str("0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5").ok(),
"GAS" => ScriptHash::from_str("0xd2a4cff31913016155e38e474a2c06d08be276cf").ok(),
_ => None,
}
},
NetworkTypeCli::NeoX => {
None
},
}
}
pub async fn parse_amount(
amount: &str,
token_hash: &ScriptHash,
rpc_client: &RpcClient<HttpProvider>,
network_type: NetworkTypeCli,
) -> Result<i64, CliError> {
let token_decimals = get_token_decimals(token_hash, rpc_client, network_type).await?;
let amount = DecimalAmount::parse(amount, token_decimals).map_err(|e| {
CliError::InvalidArgument(
format!("Invalid amount: {}", amount),
format!("Please provide a valid token amount: {}", e),
)
})?;
amount.raw_i64().ok_or_else(|| {
CliError::InvalidArgument(
"Amount is too large".to_string(),
"Please provide a smaller amount".to_string(),
)
})
}
pub async fn get_token_decimals(
token_hash: &ScriptHash,
rpc_client: &RpcClient<HttpProvider>,
network_type: NetworkTypeCli,
) -> Result<u8, CliError> {
match network_type {
NetworkTypeCli::MainNet | NetworkTypeCli::TestNet => {
let token_hash_str = token_hash.to_string();
if token_hash_str == "ef4073a0f2b305a38ec4050e4d3d28bc40ea63f5"
|| token_hash_str == "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5"
{
return Ok(0); } else if token_hash_str == "d2a4cff31913016155e38e474a2c06d08be276cf"
|| token_hash_str == "0xd2a4cff31913016155e38e474a2c06d08be276cf"
{
return Ok(8); }
},
NetworkTypeCli::NeoX => {
},
}
match rpc_client
.invoke_function(token_hash, "decimals".to_string(), vec![], None)
.await
{
Ok(result) => {
if let Some(item) = result.stack.first() {
match item {
StackItem::Integer { value } => {
value.to_u8().ok_or_else(|| {
CliError::InvalidInput(format!(
"Invalid decimals value: {}. Expected a small integer for decimals",
value
))
})
},
_ => Err(CliError::InvalidInput(format!(
"Unexpected stack item type for decimals: {:?}. Expected an integer.",
item
))),
}
} else {
Err(CliError::InvalidInput(
"Empty stack response for decimals call. Token contract may not be valid."
.to_string(),
))
}
},
Err(e) => Err(CliError::Rpc(format!("Failed to get token decimals: {}", e))),
}
}
pub fn format_token_amount(raw_amount: i64, decimals: u8) -> String {
let (sign, raw) = if raw_amount < 0 {
("-", raw_amount.unsigned_abs().to_string())
} else {
("", raw_amount.to_string())
};
let formatted = DecimalAmount::from_raw(raw, decimals).to_fixed_string();
format!("{}{}", sign, formatted)
}
pub async fn resolve_token_to_scripthash_with_network(
token: &str,
_rpc_client: &RpcClient<HttpProvider>,
network_type: NetworkTypeCli,
) -> Result<ScriptHash, CliError> {
if let Ok(script_hash) = ScriptHash::from_str(token) {
return Ok(script_hash);
}
let address = Address::from_str(token).unwrap();
if let Ok(script_hash) = address.address_to_script_hash() {
return Ok(script_hash);
}
if let Some(script_hash) = get_token_address_for_network(token, network_type) {
return Ok(script_hash);
}
Err(CliError::InvalidArgument(
format!("Could not resolve token: {}", token),
"Please provide a valid token address, symbol, or contract hash".to_string(),
))
}
pub async fn resolve_token_hash(
token: &str,
rpc_client: &RpcClient<HttpProvider>,
network_type: NetworkTypeCli,
) -> Result<String, CliError> {
let script_hash =
resolve_token_to_scripthash_with_network(token, rpc_client, network_type).await?;
Ok(script_hash.to_string())
}
pub fn load_wallet_from_state(state: &mut CliState) -> Result<&mut Wallet, CliError> {
if state.wallet.is_none() {
return Err(CliError::NoWallet);
}
Ok(state.wallet.as_mut().unwrap())
}