Skip to content

Commit 76e0517

Browse files
FroVolodFroVolodfrol
authored
fix: Fixed cli command for memo parameter (#446)
This fixed the command: ``` near tokens volodymyr.testnet send-ft usdn.testnet fro_volod.testnet amount-ft '10 usn' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' network-config testnet ``` to: ``` near tokens fro_volod.testnet send-ft usdn.testnet volodymyr.testnet '1 usn' memo Mem network-config testnet sign-with-legacy-keychain send ``` or ``` near tokens fro_volod.testnet send-ft usdn.testnet volodymyr.testnet '1 usn' memo '' --prepaid-gas '300.0 Tgas' --attached-deposit '1 yoctoNEAR' network-config testnet sign-with-legacy-keychain send ``` --------- Co-authored-by: FroVolod <frol_off@meta.ua> Co-authored-by: Vlad Frolov <frolvlad@gmail.com>
1 parent f6144ff commit 76e0517

3 files changed

Lines changed: 233 additions & 316 deletions

File tree

src/commands/tokens/send_ft/amount_ft.rs

Lines changed: 115 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,18 @@ pub struct AmountFt {
88
#[interactive_clap(skip_default_input_arg)]
99
/// Enter an amount FT to transfer:
1010
ft_transfer_amount: crate::types::ft_properties::FungibleTokenTransferAmount,
11-
#[interactive_clap(skip_default_input_arg)]
12-
/// Enter a memo for transfer (optional):
13-
memo: Option<String>,
1411
#[interactive_clap(named_arg)]
15-
/// Enter gas for function call
16-
prepaid_gas: super::preparation_ft_transfer::PrepaidGas,
12+
/// Enter a memo for transfer (optional):
13+
memo: FtTransferParams,
1714
}
1815

1916
#[derive(Debug, Clone)]
2017
pub struct AmountFtContext {
21-
pub global_context: crate::GlobalContext,
22-
pub signer_account_id: near_primitives::types::AccountId,
23-
pub ft_contract_account_id: near_primitives::types::AccountId,
24-
pub receiver_account_id: near_primitives::types::AccountId,
25-
pub ft_transfer_amount: crate::types::ft_properties::FungibleTokenTransferAmount,
26-
pub memo: Option<String>,
18+
global_context: crate::GlobalContext,
19+
signer_account_id: near_primitives::types::AccountId,
20+
ft_contract_account_id: near_primitives::types::AccountId,
21+
receiver_account_id: near_primitives::types::AccountId,
22+
ft_transfer_amount: crate::types::ft_properties::FungibleTokenTransferAmount,
2723
}
2824

2925
impl AmountFtContext {
@@ -61,14 +57,6 @@ impl AmountFtContext {
6157
ft_contract_account_id: previous_context.ft_contract_account_id,
6258
receiver_account_id: previous_context.receiver_account_id,
6359
ft_transfer_amount,
64-
memo: scope.memo.as_ref().and_then(|s| {
65-
let trimmed = s.trim();
66-
if trimmed.is_empty() {
67-
None
68-
} else {
69-
Some(trimmed.to_string())
70-
}
71-
}),
7260
})
7361
}
7462
}
@@ -113,10 +101,115 @@ impl AmountFt {
113101
.prompt()?,
114102
))
115103
}
104+
}
105+
106+
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
107+
#[interactive_clap(input_context = AmountFtContext)]
108+
#[interactive_clap(output_context = FtTransferParamsContext)]
109+
pub struct FtTransferParams {
110+
#[interactive_clap(skip_default_input_arg)]
111+
/// Enter a memo for transfer (optional):
112+
memo: Option<String>,
113+
#[interactive_clap(long = "prepaid-gas")]
114+
#[interactive_clap(skip_interactive_input)]
115+
gas: Option<crate::common::NearGas>,
116+
#[interactive_clap(long = "attached-deposit")]
117+
#[interactive_clap(skip_interactive_input)]
118+
deposit: Option<crate::types::near_token::NearToken>,
119+
#[interactive_clap(named_arg)]
120+
/// Select network
121+
network_config: crate::network_for_transaction::NetworkForTransactionArgs,
122+
}
123+
124+
#[derive(Clone)]
125+
pub struct FtTransferParamsContext(crate::commands::ActionContext);
126+
127+
impl FtTransferParamsContext {
128+
pub fn from_previous_context(
129+
previous_context: AmountFtContext,
130+
scope: &<FtTransferParams as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
131+
) -> color_eyre::eyre::Result<Self> {
132+
let get_prepopulated_transaction_after_getting_network_callback: crate::commands::GetPrepopulatedTransactionAfterGettingNetworkCallback =
133+
std::sync::Arc::new({
134+
let signer_account_id = previous_context.signer_account_id.clone();
135+
let ft_contract_account_id = previous_context.ft_contract_account_id.clone();
136+
let receiver_account_id = previous_context.receiver_account_id.clone();
137+
let ft_transfer_amount = previous_context.ft_transfer_amount.clone();
138+
let memo = scope.memo.clone();
139+
let gas = scope.gas.unwrap_or(near_gas::NearGas::from_tgas(100));
140+
let deposit = scope.deposit.unwrap_or(crate::types::near_token::NearToken::from_yoctonear(1));
141+
142+
move |network_config| {
143+
let amount_ft = super::get_amount_ft(
144+
&ft_transfer_amount,
145+
network_config,
146+
&signer_account_id,
147+
&ft_contract_account_id
148+
)?;
149+
150+
super::get_prepopulated_transaction(
151+
network_config,
152+
&ft_contract_account_id,
153+
&receiver_account_id,
154+
&signer_account_id,
155+
&amount_ft,
156+
&memo,
157+
&deposit,
158+
&gas
159+
)
160+
}
161+
});
162+
163+
let on_after_sending_transaction_callback: crate::transaction_signature_options::OnAfterSendingTransactionCallback = std::sync::Arc::new({
164+
let signer_account_id = previous_context.signer_account_id.clone();
165+
let ft_contract_account_id = previous_context.ft_contract_account_id.clone();
166+
let receiver_account_id = previous_context.receiver_account_id.clone();
167+
let ft_transfer_amount = previous_context.ft_transfer_amount.clone();
168+
169+
move |outcome_view, network_config| {
170+
let amount_ft = super::get_amount_ft(
171+
&ft_transfer_amount,
172+
network_config,
173+
&signer_account_id,
174+
&ft_contract_account_id
175+
)?;
176+
177+
if let near_primitives::views::FinalExecutionStatus::SuccessValue(_) = outcome_view.status {
178+
eprintln!(
179+
"<{signer_account_id}> has successfully transferred {amount_ft} (FT-contract: {ft_contract_account_id}) to <{receiver_account_id}>.",
180+
);
181+
}
182+
Ok(())
183+
}
184+
});
185+
186+
Ok(Self(crate::commands::ActionContext {
187+
global_context: previous_context.global_context,
188+
interacting_with_account_ids: vec![
189+
previous_context.ft_contract_account_id,
190+
previous_context.signer_account_id,
191+
previous_context.receiver_account_id,
192+
],
193+
get_prepopulated_transaction_after_getting_network_callback,
194+
on_before_signing_callback: std::sync::Arc::new(
195+
|_prepolulated_unsinged_transaction, _network_config| Ok(()),
196+
),
197+
on_before_sending_transaction_callback: std::sync::Arc::new(
198+
|_signed_transaction, _network_config| Ok(String::new()),
199+
),
200+
on_after_sending_transaction_callback,
201+
}))
202+
}
203+
}
204+
205+
impl From<FtTransferParamsContext> for crate::commands::ActionContext {
206+
fn from(item: FtTransferParamsContext) -> Self {
207+
item.0
208+
}
209+
}
116210

117-
fn input_memo(
118-
_context: &super::SendFtCommandContext,
119-
) -> color_eyre::eyre::Result<Option<String>> {
211+
impl FtTransferParams {
212+
fn input_memo(_context: &AmountFtContext) -> color_eyre::eyre::Result<Option<String>> {
120213
let input = Text::new("Enter a memo for transfer (optional):").prompt()?;
121214
Ok(if input.trim().is_empty() {
122215
None

src/commands/tokens/send_ft/mod.rs

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
use color_eyre::eyre::Context;
2+
use serde_json::{json, Value};
3+
4+
use crate::common::CallResultExt;
5+
use crate::common::JsonRpcClientExt;
6+
7+
use super::view_ft_balance::get_ft_balance;
8+
19
mod amount_ft;
2-
mod preparation_ft_transfer;
310

411
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
512
#[interactive_clap(input_context = super::TokensCommandsContext)]
@@ -11,7 +18,7 @@ pub struct SendFtCommand {
1118
#[interactive_clap(skip_default_input_arg)]
1219
/// What is the receiver account ID?
1320
receiver_account_id: crate::types::account_id::AccountId,
14-
#[interactive_clap(named_arg)]
21+
#[interactive_clap(subargs)]
1522
/// Specify amount FT
1623
amount_ft: self::amount_ft::AmountFt,
1724
}
@@ -57,3 +64,112 @@ impl SendFtCommand {
5764
)
5865
}
5966
}
67+
68+
#[allow(clippy::too_many_arguments)]
69+
#[tracing::instrument(
70+
name = "Creating a pre-populated transaction for signature ...",
71+
skip_all
72+
)]
73+
pub fn get_prepopulated_transaction(
74+
network_config: &crate::config::NetworkConfig,
75+
ft_contract_account_id: &near_primitives::types::AccountId,
76+
receiver_account_id: &near_primitives::types::AccountId,
77+
signer_id: &near_primitives::types::AccountId,
78+
amount_ft: &crate::types::ft_properties::FungibleToken,
79+
memo: &Option<String>,
80+
deposit: &crate::types::near_token::NearToken,
81+
gas: &crate::common::NearGas,
82+
) -> color_eyre::eyre::Result<crate::commands::PrepopulatedTransaction> {
83+
let args = serde_json::to_vec(&json!({
84+
"receiver_id": amount_ft.amount().to_string(),
85+
"amount": amount_ft.amount().to_string(),
86+
"memo": memo.as_ref().and_then(|s| {
87+
let trimmed = s.trim();
88+
if trimmed.is_empty() {
89+
None
90+
} else {
91+
Some(trimmed.to_string())
92+
}
93+
})
94+
}))?;
95+
96+
let action_ft_transfer = near_primitives::transaction::Action::FunctionCall(Box::new(
97+
near_primitives::transaction::FunctionCallAction {
98+
method_name: "ft_transfer".to_string(),
99+
args,
100+
gas: gas.as_gas(),
101+
deposit: deposit.as_yoctonear(),
102+
},
103+
));
104+
105+
let args = serde_json::to_vec(&json!({"account_id": receiver_account_id.to_string()}))?;
106+
107+
let call_result = network_config
108+
.json_rpc_client()
109+
.blocking_call_view_function(
110+
ft_contract_account_id,
111+
"storage_balance_of",
112+
args.clone(),
113+
near_primitives::types::Finality::Final.into(),
114+
)
115+
.wrap_err_with(||{
116+
format!("Failed to fetch query for view method: 'storage_balance_of' (contract <{}> on network <{}>)",
117+
ft_contract_account_id,
118+
network_config.network_name
119+
)
120+
})?;
121+
122+
if call_result.parse_result_from_json::<Value>()?.is_null() {
123+
let action_storage_deposit = near_primitives::transaction::Action::FunctionCall(Box::new(
124+
near_primitives::transaction::FunctionCallAction {
125+
method_name: "storage_deposit".to_string(),
126+
args,
127+
gas: gas.as_gas(),
128+
deposit: near_token::NearToken::from_millinear(100).as_yoctonear(),
129+
},
130+
));
131+
return Ok(crate::commands::PrepopulatedTransaction {
132+
signer_id: signer_id.clone(),
133+
receiver_id: ft_contract_account_id.clone(),
134+
actions: vec![action_storage_deposit, action_ft_transfer],
135+
});
136+
}
137+
138+
Ok(crate::commands::PrepopulatedTransaction {
139+
signer_id: signer_id.clone(),
140+
receiver_id: ft_contract_account_id.clone(),
141+
actions: vec![action_ft_transfer],
142+
})
143+
}
144+
145+
pub fn get_amount_ft(
146+
ft_transfer_amount: &crate::types::ft_properties::FungibleTokenTransferAmount,
147+
network_config: &crate::config::NetworkConfig,
148+
signer_account_id: &near_primitives::types::AccountId,
149+
ft_contract_account_id: &near_primitives::types::AccountId,
150+
) -> color_eyre::eyre::Result<crate::types::ft_properties::FungibleToken> {
151+
match ft_transfer_amount {
152+
crate::types::ft_properties::FungibleTokenTransferAmount::ExactAmount(ft) => Ok(ft.clone()),
153+
crate::types::ft_properties::FungibleTokenTransferAmount::MaxAmount => {
154+
let function_args = serde_json::to_vec(&json!({"account_id": signer_account_id}))?;
155+
let amount = get_ft_balance(
156+
network_config,
157+
ft_contract_account_id,
158+
function_args,
159+
near_primitives::types::Finality::Final.into(),
160+
)?
161+
.parse_result_from_json::<String>()?;
162+
let crate::types::ft_properties::FtMetadata { decimals, symbol } =
163+
crate::types::ft_properties::params_ft_metadata(
164+
ft_contract_account_id.clone(),
165+
network_config,
166+
near_primitives::types::Finality::Final.into(),
167+
)?;
168+
Ok(crate::types::ft_properties::FungibleToken::from_params_ft(
169+
amount.parse::<u128>()?,
170+
decimals,
171+
symbol,
172+
))
173+
}
174+
}
175+
}

0 commit comments

Comments
 (0)