Skip to content

Commit 93085b1

Browse files
authored
fix: Fixed information about successful transfer of "send all" ft tokens (#447)
These changes correct information about the successful transfer of ft tokens https://github.com/user-attachments/assets/d0732ff0-06d1-41bb-9b2d-c05372ffb1d2
1 parent 76e0517 commit 93085b1

File tree

3 files changed

+123
-59
lines changed

3 files changed

+123
-59
lines changed

src/commands/tokens/send_ft/amount_ft.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,16 @@ impl FtTransferParamsContext {
140140
let deposit = scope.deposit.unwrap_or(crate::types::near_token::NearToken::from_yoctonear(1));
141141

142142
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-
)?;
143+
let amount_ft = if let crate::types::ft_properties::FungibleTokenTransferAmount::ExactAmount(ft) = &ft_transfer_amount {
144+
ft.clone()
145+
} else {
146+
super::get_ft_balance_for_account(
147+
network_config,
148+
&signer_account_id,
149+
&ft_contract_account_id,
150+
near_primitives::types::Finality::Final.into()
151+
)?
152+
};
149153

150154
super::get_prepopulated_transaction(
151155
network_config,
@@ -164,21 +168,35 @@ impl FtTransferParamsContext {
164168
let signer_account_id = previous_context.signer_account_id.clone();
165169
let ft_contract_account_id = previous_context.ft_contract_account_id.clone();
166170
let receiver_account_id = previous_context.receiver_account_id.clone();
167-
let ft_transfer_amount = previous_context.ft_transfer_amount.clone();
168171

169172
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-
177173
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-
);
174+
for action in outcome_view.transaction.actions.clone() {
175+
if let near_primitives::views::ActionView::FunctionCall { method_name: _, args, gas: _, deposit: _ } = action {
176+
if let Ok(ft_transfer) = serde_json::from_slice::<crate::types::ft_properties::FtTransfer>(&args) {
177+
if let Ok(ft_balance) = super::get_ft_balance_for_account(
178+
network_config,
179+
&signer_account_id,
180+
&ft_contract_account_id,
181+
near_primitives::types::BlockId::Hash(outcome_view.receipts_outcome.last().expect("FT transfer should have at least one receipt outcome, but none was received").block_hash).into()
182+
) {
183+
let ft_transfer_amount = crate::types::ft_properties::FungibleToken::from_params_ft(
184+
ft_transfer.amount,
185+
ft_balance.decimals(),
186+
ft_balance.symbol().to_string()
187+
);
188+
eprintln!(
189+
"<{signer_account_id}> has successfully transferred {ft_transfer_amount} (FT-contract: {ft_contract_account_id}) to <{receiver_account_id}>.\nRemaining balance: {ft_balance}",
190+
);
191+
return Ok(());
192+
}
193+
}
194+
}
195+
}
181196
}
197+
eprintln!(
198+
"<{signer_account_id}> has successfully transferred fungible tokens (FT-contract: {ft_contract_account_id}) to <{receiver_account_id}>.",
199+
);
182200
Ok(())
183201
}
184202
});

src/commands/tokens/send_ft/mod.rs

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -80,29 +80,22 @@ pub fn get_prepopulated_transaction(
8080
deposit: &crate::types::near_token::NearToken,
8181
gas: &crate::common::NearGas,
8282
) -> 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-
}))?;
83+
let args_ft_transfer = serde_json::to_vec(&crate::types::ft_properties::FtTransfer {
84+
receiver_id: receiver_account_id.clone(),
85+
amount: amount_ft.amount(),
86+
memo: memo.clone(),
87+
})?;
9588

9689
let action_ft_transfer = near_primitives::transaction::Action::FunctionCall(Box::new(
9790
near_primitives::transaction::FunctionCallAction {
9891
method_name: "ft_transfer".to_string(),
99-
args,
92+
args: args_ft_transfer,
10093
gas: gas.as_gas(),
10194
deposit: deposit.as_yoctonear(),
10295
},
10396
));
10497

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

107100
let call_result = network_config
108101
.json_rpc_client()
@@ -131,45 +124,40 @@ pub fn get_prepopulated_transaction(
131124
return Ok(crate::commands::PrepopulatedTransaction {
132125
signer_id: signer_id.clone(),
133126
receiver_id: ft_contract_account_id.clone(),
134-
actions: vec![action_storage_deposit, action_ft_transfer],
127+
actions: vec![action_storage_deposit, action_ft_transfer.clone()],
135128
});
136129
}
137130

138131
Ok(crate::commands::PrepopulatedTransaction {
139132
signer_id: signer_id.clone(),
140133
receiver_id: ft_contract_account_id.clone(),
141-
actions: vec![action_ft_transfer],
134+
actions: vec![action_ft_transfer.clone()],
142135
})
143136
}
144137

145-
pub fn get_amount_ft(
146-
ft_transfer_amount: &crate::types::ft_properties::FungibleTokenTransferAmount,
138+
fn get_ft_balance_for_account(
147139
network_config: &crate::config::NetworkConfig,
148140
signer_account_id: &near_primitives::types::AccountId,
149141
ft_contract_account_id: &near_primitives::types::AccountId,
142+
block_reference: near_primitives::types::BlockReference,
150143
) -> 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-
}
144+
let function_args = serde_json::to_vec(&json!({"account_id": signer_account_id}))?;
145+
let amount = get_ft_balance(
146+
network_config,
147+
ft_contract_account_id,
148+
function_args,
149+
block_reference,
150+
)?
151+
.parse_result_from_json::<String>()?;
152+
let crate::types::ft_properties::FtMetadata { decimals, symbol } =
153+
crate::types::ft_properties::params_ft_metadata(
154+
ft_contract_account_id.clone(),
155+
network_config,
156+
near_primitives::types::Finality::Final.into(),
157+
)?;
158+
Ok(crate::types::ft_properties::FungibleToken::from_params_ft(
159+
amount.parse::<u128>()?,
160+
decimals,
161+
symbol,
162+
))
175163
}

src/types/ft_properties.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use color_eyre::eyre::{Context, ContextCompat};
2+
use serde::de::{Deserialize, Deserializer};
3+
use serde::ser::{Serialize, Serializer};
24

35
use crate::common::CallResultExt;
46
use crate::common::JsonRpcClientExt;
@@ -218,6 +220,32 @@ pub fn params_ft_metadata(
218220
Ok(ft_metadata)
219221
}
220222

223+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
224+
pub struct FtTransfer {
225+
pub receiver_id: near_primitives::types::AccountId,
226+
#[serde(deserialize_with = "parse_u128_string", serialize_with = "to_string")]
227+
pub amount: u128,
228+
#[serde(skip_serializing_if = "Option::is_none")]
229+
pub memo: Option<String>,
230+
}
231+
232+
fn parse_u128_string<'de, D>(deserializer: D) -> color_eyre::eyre::Result<u128, D::Error>
233+
where
234+
D: Deserializer<'de>,
235+
{
236+
String::deserialize(deserializer)?
237+
.parse::<u128>()
238+
.map_err(serde::de::Error::custom)
239+
}
240+
241+
fn to_string<S, T: ToString>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
242+
where
243+
S: Serializer,
244+
{
245+
let s = value.to_string();
246+
String::serialize(&s, serializer)
247+
}
248+
221249
#[cfg(test)]
222250
mod tests {
223251
use super::*;
@@ -269,4 +297,34 @@ mod tests {
269297
assert_eq!(ft_transfer_amount.to_string(), "all".to_string());
270298
assert_eq!(ft_transfer_amount, FungibleTokenTransferAmount::MaxAmount);
271299
}
300+
#[test]
301+
fn ft_transfer_with_memo_to_vec_u8() {
302+
let ft_transfer = serde_json::to_vec(&crate::types::ft_properties::FtTransfer {
303+
receiver_id: "fro_volod.testnet".parse().unwrap(),
304+
amount: FungibleToken::from_str("0.123456 USDC").unwrap().amount(),
305+
memo: Some("Memo".to_string()),
306+
})
307+
.unwrap();
308+
assert_eq!(
309+
serde_json::from_slice::<serde_json::Value>(&ft_transfer)
310+
.unwrap()
311+
.to_string(),
312+
"{\"amount\":\"123456\",\"memo\":\"Memo\",\"receiver_id\":\"fro_volod.testnet\"}"
313+
);
314+
}
315+
#[test]
316+
fn ft_transfer_without_memo_to_vec_u8() {
317+
let ft_transfer = serde_json::to_vec(&crate::types::ft_properties::FtTransfer {
318+
receiver_id: "fro_volod.testnet".parse().unwrap(),
319+
amount: FungibleToken::from_str("0.123456 USDC").unwrap().amount(),
320+
memo: None,
321+
})
322+
.unwrap();
323+
assert_eq!(
324+
serde_json::from_slice::<serde_json::Value>(&ft_transfer)
325+
.unwrap()
326+
.to_string(),
327+
"{\"amount\":\"123456\",\"receiver_id\":\"fro_volod.testnet\"}"
328+
);
329+
}
272330
}

0 commit comments

Comments
 (0)