Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fixed cli command for memo parameter #446

Merged
merged 11 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 122 additions & 22 deletions src/commands/tokens/send_ft/amount_ft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,18 @@ pub struct AmountFt {
#[interactive_clap(skip_default_input_arg)]
/// Enter an amount FT to transfer:
ft_transfer_amount: crate::types::ft_properties::FungibleTokenTransferAmount,
#[interactive_clap(skip_default_input_arg)]
/// Enter a memo for transfer (optional):
memo: Option<String>,
#[interactive_clap(named_arg)]
/// Enter gas for function call
prepaid_gas: super::preparation_ft_transfer::PrepaidGas,
/// Enter a memo for transfer (optional):
memo: FtTransferParams,
}

#[derive(Debug, Clone)]
pub struct AmountFtContext {
pub global_context: crate::GlobalContext,
pub signer_account_id: near_primitives::types::AccountId,
pub ft_contract_account_id: near_primitives::types::AccountId,
pub receiver_account_id: near_primitives::types::AccountId,
pub ft_transfer_amount: crate::types::ft_properties::FungibleTokenTransferAmount,
pub memo: Option<String>,
global_context: crate::GlobalContext,
signer_account_id: near_primitives::types::AccountId,
ft_contract_account_id: near_primitives::types::AccountId,
receiver_account_id: near_primitives::types::AccountId,
ft_transfer_amount: crate::types::ft_properties::FungibleTokenTransferAmount,
}

impl AmountFtContext {
Expand Down Expand Up @@ -61,14 +57,6 @@ impl AmountFtContext {
ft_contract_account_id: previous_context.ft_contract_account_id,
receiver_account_id: previous_context.receiver_account_id,
ft_transfer_amount,
memo: scope.memo.as_ref().and_then(|s| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer this to be honest, over custom type that is not needed yet in the end.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}),
})
}
}
Expand Down Expand Up @@ -113,10 +101,122 @@ impl AmountFt {
.prompt()?,
))
}
}

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = AmountFtContext)]
#[interactive_clap(output_context = FtTransferParamsContext)]
pub struct FtTransferParams {
#[interactive_clap(skip_default_input_arg)]
/// Enter a memo for transfer (optional):
memo: Option<String>,
#[interactive_clap(long = "prepaid-gas")]
#[interactive_clap(skip_interactive_input)]
gas: Option<crate::common::NearGas>,
#[interactive_clap(long = "attached-deposit")]
#[interactive_clap(skip_interactive_input)]
deposit: Option<crate::types::near_token::NearToken>,
#[interactive_clap(named_arg)]
/// Select network
network_config: crate::network_for_transaction::NetworkForTransactionArgs,
}

#[derive(Clone)]
pub struct FtTransferParamsContext(crate::commands::ActionContext);

impl FtTransferParamsContext {
pub fn from_previous_context(
previous_context: AmountFtContext,
scope: &<FtTransferParams as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
let get_prepopulated_transaction_after_getting_network_callback: crate::commands::GetPrepopulatedTransactionAfterGettingNetworkCallback =
std::sync::Arc::new({
let signer_account_id = previous_context.signer_account_id.clone();
let ft_contract_account_id = previous_context.ft_contract_account_id.clone();
let receiver_account_id = previous_context.receiver_account_id.clone();
let ft_transfer_amount = previous_context.ft_transfer_amount.clone();
let memo = scope.memo.as_ref().and_then(|s| {
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
});
let gas = scope.gas.unwrap_or(near_gas::NearGas::from_tgas(100));
let deposit = scope.deposit.unwrap_or(crate::types::near_token::NearToken::from_yoctonear(1));

move |network_config| {
let amount_ft = super::get_amount_ft(
&ft_transfer_amount,
network_config,
&signer_account_id,
&ft_contract_account_id
)?;

super::get_prepopulated_transaction(
network_config,
&ft_contract_account_id,
&receiver_account_id,
&signer_account_id,
&amount_ft,
&memo,
&deposit,
&gas
)
}
});

let on_after_sending_transaction_callback: crate::transaction_signature_options::OnAfterSendingTransactionCallback = std::sync::Arc::new({
let signer_account_id = previous_context.signer_account_id.clone();
let ft_contract_account_id = previous_context.ft_contract_account_id.clone();
let receiver_account_id = previous_context.receiver_account_id.clone();
let ft_transfer_amount = previous_context.ft_transfer_amount.clone();

move |outcome_view, network_config| {
let amount_ft = super::get_amount_ft(
&ft_transfer_amount,
network_config,
&signer_account_id,
&ft_contract_account_id
)?;

if let near_primitives::views::FinalExecutionStatus::SuccessValue(_) = outcome_view.status {
eprintln!(
"<{signer_account_id}> has successfully transferred {amount_ft} (FT-contract: {ft_contract_account_id}) to <{receiver_account_id}>.",
);
}
Ok(())
}
});

Ok(Self(crate::commands::ActionContext {
global_context: previous_context.global_context,
interacting_with_account_ids: vec![
previous_context.ft_contract_account_id,
previous_context.signer_account_id,
previous_context.receiver_account_id,
],
get_prepopulated_transaction_after_getting_network_callback,
on_before_signing_callback: std::sync::Arc::new(
|_prepolulated_unsinged_transaction, _network_config| Ok(()),
),
on_before_sending_transaction_callback: std::sync::Arc::new(
|_signed_transaction, _network_config| Ok(String::new()),
),
on_after_sending_transaction_callback,
}))
}
}

impl From<FtTransferParamsContext> for crate::commands::ActionContext {
fn from(item: FtTransferParamsContext) -> Self {
item.0
}
}

fn input_memo(
_context: &super::SendFtCommandContext,
) -> color_eyre::eyre::Result<Option<String>> {
impl FtTransferParams {
fn input_memo(_context: &AmountFtContext) -> color_eyre::eyre::Result<Option<String>> {
let input = Text::new("Enter a memo for transfer (optional):").prompt()?;
Ok(if input.trim().is_empty() {
None
Expand Down
121 changes: 119 additions & 2 deletions src/commands/tokens/send_ft/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use color_eyre::eyre::Context;
use serde_json::{json, Value};

use crate::common::CallResultExt;
use crate::common::JsonRpcClientExt;

use super::view_ft_balance::get_ft_balance;

mod amount_ft;
mod preparation_ft_transfer;

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = super::TokensCommandsContext)]
Expand All @@ -11,7 +18,7 @@ pub struct SendFtCommand {
#[interactive_clap(skip_default_input_arg)]
/// What is the receiver account ID?
receiver_account_id: crate::types::account_id::AccountId,
#[interactive_clap(named_arg)]
#[interactive_clap(subargs)]
/// Specify amount FT
amount_ft: self::amount_ft::AmountFt,
}
Expand Down Expand Up @@ -57,3 +64,113 @@ impl SendFtCommand {
)
}
}

#[allow(clippy::too_many_arguments)]
#[tracing::instrument(
name = "Creating a pre-populated transaction for signature ...",
skip_all
)]
pub fn get_prepopulated_transaction(
network_config: &crate::config::NetworkConfig,
ft_contract_account_id: &near_primitives::types::AccountId,
receiver_account_id: &near_primitives::types::AccountId,
signer_id: &near_primitives::types::AccountId,
amount_ft: &crate::types::ft_properties::FungibleToken,
memo: &Option<String>,
deposit: &crate::types::near_token::NearToken,
gas: &crate::common::NearGas,
) -> color_eyre::eyre::Result<crate::commands::PrepopulatedTransaction> {
let args = serde_json::to_vec(&json!({
"receiver_id": amount_ft.amount().to_string(),
"amount": amount_ft.amount().to_string(),
"memo": memo.as_ref().and_then(|s| {
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
})
}))?;

let action_ft_transfer = near_primitives::transaction::Action::FunctionCall(Box::new(
near_primitives::transaction::FunctionCallAction {
method_name: "ft_transfer".to_string(),
args,
gas: gas.as_gas(),
deposit: deposit.as_yoctonear(),
},
));

let args = serde_json::to_vec(&json!({"account_id": receiver_account_id.to_string()}))?;

let call_result = network_config
.json_rpc_client()
.blocking_call_view_function(
ft_contract_account_id,
"storage_balance_of",
args.clone(),
near_primitives::types::Finality::Final.into(),
)
.wrap_err_with(||{
format!("Failed to fetch query for view method: 'storage_balance_of' (contract <{}> on network <{}>)",
ft_contract_account_id,
network_config.network_name
)
})?;

if call_result.parse_result_from_json::<Value>()?.is_null() {
let action_storage_deposit = near_primitives::transaction::Action::FunctionCall(Box::new(
near_primitives::transaction::FunctionCallAction {
method_name: "storage_deposit".to_string(),
args,
gas: gas.as_gas(),
deposit: near_token::NearToken::from_millinear(100).as_yoctonear(),
},
));
return Ok(crate::commands::PrepopulatedTransaction {
signer_id: signer_id.clone(),
receiver_id: ft_contract_account_id.clone(),
actions: vec![action_storage_deposit, action_ft_transfer.clone()],
});
}

Ok(crate::commands::PrepopulatedTransaction {
signer_id: signer_id.clone(),
receiver_id: ft_contract_account_id.clone(),
actions: vec![action_ft_transfer.clone()],
})
}

pub fn get_amount_ft(
ft_transfer_amount: &crate::types::ft_properties::FungibleTokenTransferAmount,
network_config: &crate::config::NetworkConfig,
signer_account_id: &near_primitives::types::AccountId,
ft_contract_account_id: &near_primitives::types::AccountId,
) -> color_eyre::eyre::Result<crate::types::ft_properties::FungibleToken> {
match ft_transfer_amount {
crate::types::ft_properties::FungibleTokenTransferAmount::ExactAmount(ft) => Ok(ft.clone()),
crate::types::ft_properties::FungibleTokenTransferAmount::MaxAmount => {
let function_args =
serde_json::to_vec(&json!({"account_id": signer_account_id.to_string()}))?;
let amount = get_ft_balance(
network_config,
ft_contract_account_id,
function_args,
near_primitives::types::Finality::Final.into(),
)?
.parse_result_from_json::<String>()?;
let crate::types::ft_properties::FtMetadata { decimals, symbol } =
crate::types::ft_properties::params_ft_metadata(
ft_contract_account_id.clone(),
network_config,
near_primitives::types::Finality::Final.into(),
)?;
Ok(crate::types::ft_properties::FungibleToken::from_params_ft(
amount.parse::<u128>()?,
decimals,
symbol,
))
}
}
}
Loading
Loading