use std::env;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use color_eyre::eyre::Result;
use masp_primitives::zip32::sapling::PseudoExtendedKey;
use masp_primitives::zip32::{
ExtendedFullViewingKey as MaspExtendedViewingKey,
ExtendedSpendingKey as MaspExtendedSpendingKey,
};
use namada_core::masp::{
BalanceOwner, ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress,
TransferSource, TransferTarget,
};
use namada_sdk::address::{Address, InternalAddress};
use namada_sdk::chain::ChainId;
use namada_sdk::ethereum_events::EthAddress;
use namada_sdk::ibc::trace::{ibc_token, is_ibc_denom, is_nft_trace};
use namada_sdk::io::Io;
use namada_sdk::key::*;
use namada_sdk::masp::ShieldedWallet;
use namada_sdk::masp::fs::FsShieldedUtils;
use namada_sdk::wallet::{DatedSpendingKey, DatedViewingKey, Wallet};
use namada_sdk::{Namada, NamadaImpl};
use super::args;
use crate::cli::utils;
use crate::config::global::GlobalConfig;
use crate::config::{Config, genesis};
use crate::wallet::CliWalletUtils;
use crate::{wallet, wasm_loader};
struct SkipErr;
pub const ENV_VAR_WASM_DIR: &str = "NAMADA_WASM_DIR";
pub const ENV_VAR_CHAIN_ID: &str = "NAMADA_CHAIN_ID";
pub type WalletAddress = FromContext<Address>;
pub type WalletAddrOrNativeToken = FromContext<AddrOrNativeToken>;
pub type WalletSpendingKey = FromContext<PseudoExtendedKey>;
pub type WalletDatedSpendingKey = FromContext<DatedSpendingKey>;
pub type WalletPaymentAddr = FromContext<PaymentAddress>;
pub type WalletViewingKey = FromContext<ExtendedViewingKey>;
pub type WalletDatedViewingKey = FromContext<DatedViewingKey>;
pub type WalletTransferSource = FromContext<TransferSource>;
pub type WalletTransferTarget = FromContext<TransferTarget>;
pub type WalletKeypair = FromContext<common::SecretKey>;
pub type WalletPublicKey = FromContext<common::PublicKey>;
pub type WalletBalanceOwner = FromContext<BalanceOwner>;
pub type ConfigRpcAddress = FromContext<tendermint_rpc::Url>;
#[derive(Clone, Debug)]
pub struct AddrOrNativeToken(Address);
impl From<AddrOrNativeToken> for Address {
fn from(AddrOrNativeToken(addr): AddrOrNativeToken) -> Self {
addr
}
}
impl FromStr for AddrOrNativeToken {
type Err = <Address as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let addr = Address::from_str(s)?;
Ok(Self(addr))
}
}
impl From<tendermint_rpc::Url> for ConfigRpcAddress {
fn from(value: tendermint_rpc::Url) -> Self {
FromContext::new(value.to_string())
}
}
#[derive(Debug)]
pub struct Context {
pub global_args: args::Global,
pub global_config: GlobalConfig,
pub chain: Option<ChainContext>,
}
#[derive(Debug)]
pub struct ChainContext {
pub wallet: Wallet<CliWalletUtils>,
pub config: Config,
pub shielded: ShieldedWallet<FsShieldedUtils>,
pub native_token: Address,
}
pub fn wasm_dir_from_env_or_args(
global_args: &args::Global,
) -> Option<PathBuf> {
wasm_dir_from_env_or(global_args.wasm_dir.as_ref())
}
pub fn wasm_dir_from_env_or<P: AsRef<Path>>(
wasm_dir: Option<&P>,
) -> Option<PathBuf> {
wasm_dir
.map(|wasm_dir| wasm_dir.as_ref().to_owned())
.or_else(|| {
env::var(ENV_VAR_WASM_DIR)
.ok()
.map(|wasm_dir| wasm_dir.into())
})
}
impl Context {
pub fn new<IO: Io>(global_args: args::Global) -> Result<Self> {
let global_config = read_or_try_new_global_config(&global_args);
let env_var_chain_id = std::env::var(ENV_VAR_CHAIN_ID)
.ok()
.and_then(|chain_id| ChainId::from_str(&chain_id).ok());
let chain_id = env_var_chain_id
.as_ref()
.or(global_args.chain_id.as_ref())
.or(global_config.default_chain_id.as_ref());
let chain = match chain_id {
Some(chain_id) if !global_args.is_pre_genesis => {
let mut config =
Config::load(&global_args.base_dir, chain_id, None);
let chain_dir = global_args.base_dir.join(chain_id.as_str());
let native_token =
genesis::chain::Finalized::read_native_token(&chain_dir)
.expect("Missing genesis files");
let wallet = if wallet::exists(&chain_dir) {
wallet::load(&chain_dir).unwrap()
} else {
panic!(
"Could not find wallet at {}.",
chain_dir.to_string_lossy()
);
};
if let Some(wasm_dir) = wasm_dir_from_env_or_args(&global_args)
{
config.wasm_dir = wasm_dir;
}
Some(ChainContext {
wallet,
config,
shielded: FsShieldedUtils::new(chain_dir),
native_token,
})
}
_ => None,
};
Ok(Self {
global_args,
global_config,
chain,
})
}
pub fn take_chain_or_exit(self) -> ChainContext {
self.chain
.unwrap_or_else(|| safe_exit_on_missing_chain_context())
}
pub fn borrow_chain_or_exit(&self) -> &ChainContext {
self.chain
.as_ref()
.unwrap_or_else(|| safe_exit_on_missing_chain_context())
}
pub fn borrow_mut_chain_or_exit(&mut self) -> &mut ChainContext {
self.chain
.as_mut()
.unwrap_or_else(|| safe_exit_on_missing_chain_context())
}
pub fn to_sdk<C, IO>(self, client: C, io: IO) -> impl Namada
where
C: namada_sdk::io::Client + Sync,
IO: Io,
{
let chain_ctx = self.take_chain_or_exit();
NamadaImpl::native_new(
client,
chain_ctx.wallet,
chain_ctx.shielded,
io,
chain_ctx.native_token,
)
}
}
fn safe_exit_on_missing_chain_context() -> ! {
eprintln!(
"Failed to construct Namada chain context. If no chain is configured, \
you may need to run `namada client utils join-network`. If the chain \
is configured, you may need to set the chain id with `--chain-id \
<chainid>`, via the env var `{ENV_VAR_CHAIN_ID}`, or configure the \
default chain id in the `global-config.toml` file. If you do intend \
to run pre-genesis operations, pass the `--pre-genesis` flag as the \
first argument to the command."
);
utils::safe_exit(1)
}
impl ChainContext {
pub fn get<T>(&self, from_context: &FromContext<T>) -> T
where
T: ArgFromContext,
{
from_context.arg_from_ctx(self).unwrap()
}
pub fn get_opt<T>(&self, from_context: &Option<FromContext<T>>) -> Option<T>
where
T: ArgFromContext,
{
from_context
.as_ref()
.map(|from_context| from_context.arg_from_ctx(self).unwrap())
}
pub fn get_cached<T>(&mut self, from_context: &FromContext<T>) -> T
where
T: ArgFromMutContext,
{
from_context.arg_from_mut_ctx(self).unwrap()
}
pub fn get_opt_cached<T>(
&mut self,
from_context: &Option<FromContext<T>>,
) -> Option<T>
where
T: ArgFromMutContext,
{
from_context
.as_ref()
.map(|from_context| from_context.arg_from_mut_ctx(self).unwrap())
}
pub fn wasm_dir(&self) -> PathBuf {
self.config.ledger.chain_dir().join(&self.config.wasm_dir)
}
pub fn read_wasm(&self, file_name: impl AsRef<Path>) -> Vec<u8> {
wasm_loader::read_wasm_or_exit(self.wasm_dir(), file_name)
}
}
pub fn read_or_try_new_global_config(
global_args: &args::Global,
) -> GlobalConfig {
GlobalConfig::read(&global_args.base_dir).unwrap_or_else(|err| {
eprintln!("Error reading global config: {}", err);
super::safe_exit(1)
})
}
#[derive(Debug, Clone)]
pub struct FromContext<T> {
pub(crate) raw: String,
phantom: PhantomData<T>,
}
impl<T> FromContext<T> {
pub fn new(raw: String) -> FromContext<T> {
Self {
raw,
phantom: PhantomData,
}
}
}
impl From<FromContext<Address>> for FromContext<AddrOrNativeToken> {
fn from(value: FromContext<Address>) -> Self {
Self {
raw: value.raw,
phantom: PhantomData,
}
}
}
impl FromContext<TransferSource> {
pub fn to_address(&self) -> FromContext<Address> {
FromContext::<Address> {
raw: self.raw.clone(),
phantom: PhantomData,
}
}
pub fn to_spending_key(&self) -> FromContext<ExtendedSpendingKey> {
FromContext::<ExtendedSpendingKey> {
raw: self.raw.clone(),
phantom: PhantomData,
}
}
}
impl FromContext<TransferTarget> {
pub fn to_address(&self) -> FromContext<Address> {
FromContext::<Address> {
raw: self.raw.clone(),
phantom: PhantomData,
}
}
pub fn to_payment_address(&self) -> FromContext<PaymentAddress> {
FromContext::<PaymentAddress> {
raw: self.raw.clone(),
phantom: PhantomData,
}
}
}
impl<T> FromContext<T>
where
T: ArgFromContext,
{
fn arg_from_ctx(&self, ctx: &ChainContext) -> Result<T, String> {
T::arg_from_ctx(ctx, &self.raw)
}
}
impl<T> FromContext<T>
where
T: ArgFromMutContext,
{
fn arg_from_mut_ctx(&self, ctx: &mut ChainContext) -> Result<T, String> {
T::arg_from_mut_ctx(ctx, &self.raw)
}
}
pub trait ArgFromContext: Sized {
fn arg_from_ctx(
ctx: &ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String>;
}
pub trait ArgFromMutContext: Sized {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String>;
}
impl ArgFromContext for Address {
fn arg_from_ctx(
ctx: &ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
FromStr::from_str(raw)
.or_else(|_| {
if raw.len() == 42 && raw.starts_with("0x") {
{
raw.parse::<EthAddress>()
.map(|addr| {
Address::Internal(InternalAddress::Erc20(addr))
})
.map_err(|_| SkipErr)
}
} else {
Err(SkipErr)
}
})
.or_else(|_| {
is_ibc_denom(raw)
.map(|(trace_path, base_denom)| {
let base_token = ctx
.wallet
.find_address(&base_denom)
.filter(|addr| **addr == ctx.native_token)
.map(|addr| addr.to_string())
.unwrap_or(base_denom);
let ibc_denom = format!("{trace_path}/{base_token}");
ibc_token(ibc_denom)
})
.ok_or(SkipErr)
})
.or_else(|_| {
is_nft_trace(raw)
.map(|(_, _, _)| ibc_token(raw))
.ok_or(SkipErr)
})
.or_else(|_| {
ctx.wallet
.find_address(raw)
.map(|x| x.into_owned())
.ok_or(SkipErr)
})
.map_err(|_| format!("Unknown address {raw}"))
}
}
impl ArgFromContext for AddrOrNativeToken {
fn arg_from_ctx(
ctx: &ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
if let Ok(addr) = Address::arg_from_ctx(ctx, raw) {
Ok(Self(addr))
} else {
Ok(Self(ctx.native_token.clone()))
}
}
}
impl ArgFromContext for tendermint_rpc::Url {
fn arg_from_ctx(
ctx: &ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
if raw.as_ref().is_empty() {
return Self::from_str(
&ctx.config
.ledger
.cometbft
.rpc
.laddr
.to_string()
.replace("tcp", "http"),
)
.map_err(|err| format!("Invalid Tendermint address: {err}"));
}
Self::from_str(raw.as_ref())
.map_err(|err| format!("Invalid Tendermint address: {err}"))
}
}
impl ArgFromMutContext for common::SecretKey {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
FromStr::from_str(raw).or_else(|_parse_err| {
ctx.wallet
.find_secret_key(raw, None)
.map_err(|_find_err| format!("Unknown key {}", raw))
})
}
}
impl ArgFromContext for common::PublicKey {
fn arg_from_ctx(
ctx: &ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
FromStr::from_str(raw)
.map_err(|_| SkipErr)
.or_else(|SkipErr| {
FromStr::from_str(raw).map_err(|_| SkipErr).and_then(
|pkh: PublicKeyHash| {
ctx.wallet
.find_public_key_by_pkh(&pkh)
.map_err(|_| SkipErr)
},
)
})
.or_else(|SkipErr| {
ctx.wallet.find_public_key(raw).map_err(|_| SkipErr)
})
.or_else(|SkipErr| {
let Address::Implicit(implicit_addr) =
Address::decode(raw).map_err(|_| SkipErr)?
else {
return Err(SkipErr);
};
ctx.wallet
.find_public_key_from_implicit_addr(&implicit_addr)
.map_err(|_| SkipErr)
})
.map_err(|SkipErr| {
format!("Couldn't look-up public key associated with {raw:?}")
})
}
}
impl ArgFromMutContext for ExtendedSpendingKey {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
FromStr::from_str(raw).or_else(|_parse_err| {
ctx.wallet
.find_spending_key(raw, None)
.map_err(|_find_err| format!("Unknown spending key {}", raw))
})
}
}
impl ArgFromMutContext for PseudoExtendedKey {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
ExtendedSpendingKey::from_str(raw)
.map(|x| PseudoExtendedKey::from(MaspExtendedSpendingKey::from(x)))
.or_else(|_parse_err| {
ExtendedViewingKey::from_str(raw).map(|x| {
PseudoExtendedKey::from(MaspExtendedViewingKey::from(x))
})
})
.or_else(|_parse_err| {
ctx.wallet
.find_spending_key(raw, None)
.map(|k| {
PseudoExtendedKey::from(MaspExtendedSpendingKey::from(
k,
))
})
.map_err(|_find_err| {
format!("Unknown spending key {}", raw)
})
})
.or_else(|_parse_err| {
ctx.wallet
.find_viewing_key(raw)
.copied()
.map(|k| {
PseudoExtendedKey::from(MaspExtendedViewingKey::from(k))
})
.map_err(|_find_err| format!("Unknown viewing key {}", raw))
})
}
}
impl ArgFromMutContext for DatedSpendingKey {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
FromStr::from_str(raw).or_else(|_parse_err| {
let sk = ctx
.wallet
.find_spending_key(raw, None)
.map_err(|_find_err| format!("Unknown spending key {}", raw))?;
let birthday = ctx.wallet.find_birthday(raw);
Ok(DatedSpendingKey::new(sk, birthday.copied()))
})
}
}
impl ArgFromMutContext for ExtendedViewingKey {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
FromStr::from_str(raw).or_else(|_parse_err| {
ctx.wallet
.find_viewing_key(raw)
.copied()
.map_err(|_find_err| format!("Unknown viewing key {}", raw))
})
}
}
impl ArgFromMutContext for DatedViewingKey {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
FromStr::from_str(raw).or_else(|_parse_err| {
let vk = ctx
.wallet
.find_viewing_key(raw)
.map_err(|_find_err| format!("Unknown viewing key {}", raw))?;
let birthday = ctx.wallet.find_birthday(raw);
Ok(DatedViewingKey::new(*vk, birthday.copied()))
})
}
}
impl ArgFromContext for PaymentAddress {
fn arg_from_ctx(
ctx: &ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
FromStr::from_str(raw).or_else(|_parse_err| {
ctx.wallet
.find_payment_addr(raw)
.cloned()
.ok_or_else(|| format!("Unknown payment address {}", raw))
})
}
}
impl ArgFromMutContext for TransferSource {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
Address::arg_from_ctx(ctx, raw)
.map(Self::Address)
.or_else(|_| {
ExtendedSpendingKey::arg_from_mut_ctx(ctx, raw).map(|x| {
Self::ExtendedKey(PseudoExtendedKey::from(
MaspExtendedSpendingKey::from(x),
))
})
})
.or_else(|_| {
ExtendedViewingKey::arg_from_mut_ctx(ctx, raw).map(|x| {
Self::ExtendedKey(PseudoExtendedKey::from(
MaspExtendedViewingKey::from(x),
))
})
})
}
}
impl ArgFromContext for TransferTarget {
fn arg_from_ctx(
ctx: &ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
Address::arg_from_ctx(ctx, raw)
.map(Self::Address)
.or_else(|_| {
PaymentAddress::arg_from_ctx(ctx, raw).map(Self::PaymentAddress)
})
}
}
impl ArgFromMutContext for BalanceOwner {
fn arg_from_mut_ctx(
ctx: &mut ChainContext,
raw: impl AsRef<str>,
) -> Result<Self, String> {
let raw = raw.as_ref();
Address::arg_from_ctx(ctx, raw)
.map(Self::Address)
.or_else(|_| {
ExtendedViewingKey::arg_from_mut_ctx(ctx, raw)
.map(Self::FullViewingKey)
})
.map_err(|_| {
format!(
"Could not find {raw} in the wallet, nor parse it as a \
transparent address or as a MASP viewing key"
)
})
}
}